@massu/core 1.7.0 → 1.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/dist/cli.js CHANGED
@@ -1910,6 +1910,283 @@ var init_template_engine = __esm({
1910
1910
  }
1911
1911
  });
1912
1912
 
1913
+ // src/lib/settings-local.ts
1914
+ import {
1915
+ closeSync,
1916
+ existsSync as existsSync4,
1917
+ fsyncSync,
1918
+ mkdirSync as mkdirSync2,
1919
+ openSync,
1920
+ readFileSync as readFileSync3,
1921
+ renameSync,
1922
+ rmSync,
1923
+ writeSync
1924
+ } from "fs";
1925
+ import { dirname as dirname3, resolve as resolve3 } from "path";
1926
+ function atomicWriteFile(targetPath, content, mode = 420) {
1927
+ const tmpPath = `${targetPath}.${process.pid}.tmp`;
1928
+ try {
1929
+ const fd = openSync(tmpPath, "w", mode);
1930
+ try {
1931
+ const buf = Buffer.from(content, "utf-8");
1932
+ writeSync(fd, buf, 0, buf.length, 0);
1933
+ fsyncSync(fd);
1934
+ } finally {
1935
+ closeSync(fd);
1936
+ }
1937
+ renameSync(tmpPath, targetPath);
1938
+ } catch (err) {
1939
+ if (existsSync4(tmpPath)) {
1940
+ try {
1941
+ rmSync(tmpPath, { force: true });
1942
+ } catch {
1943
+ }
1944
+ }
1945
+ throw err;
1946
+ }
1947
+ }
1948
+ function readSettingsAtPath(absolutePath) {
1949
+ if (!existsSync4(absolutePath)) {
1950
+ return {};
1951
+ }
1952
+ try {
1953
+ const raw = readFileSync3(absolutePath, "utf-8");
1954
+ const parsed = JSON.parse(raw);
1955
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1956
+ return {};
1957
+ }
1958
+ return parsed;
1959
+ } catch {
1960
+ return {};
1961
+ }
1962
+ }
1963
+ function readSettingsLocal(claudeDir) {
1964
+ return readSettingsAtPath(resolve3(claudeDir, "settings.local.json"));
1965
+ }
1966
+ function writeSettingsLocalAtomic(claudeDir, settings) {
1967
+ const targetPath = resolve3(claudeDir, "settings.local.json");
1968
+ const dir = dirname3(targetPath);
1969
+ if (!existsSync4(dir)) {
1970
+ mkdirSync2(dir, { recursive: true });
1971
+ }
1972
+ const content = JSON.stringify(settings, null, 2) + "\n";
1973
+ atomicWriteFile(targetPath, content);
1974
+ }
1975
+ var init_settings_local = __esm({
1976
+ "src/lib/settings-local.ts"() {
1977
+ "use strict";
1978
+ }
1979
+ });
1980
+
1981
+ // src/permissions.ts
1982
+ import { createHash } from "crypto";
1983
+ import { homedir as homedir2 } from "os";
1984
+ import { join as join2 } from "path";
1985
+ function canonicalJson(value) {
1986
+ if (value === null || typeof value !== "object") {
1987
+ return JSON.stringify(value);
1988
+ }
1989
+ if (Array.isArray(value)) {
1990
+ return "[" + value.map(canonicalJson).join(",") + "]";
1991
+ }
1992
+ const keys = Object.keys(value).sort();
1993
+ const parts = keys.map((k3) => {
1994
+ const v3 = value[k3];
1995
+ if (v3 === void 0) return "";
1996
+ return JSON.stringify(k3) + ":" + canonicalJson(v3);
1997
+ }).filter((s) => s !== "");
1998
+ return "{" + parts.join(",") + "}";
1999
+ }
2000
+ function sha256Hex(input) {
2001
+ return createHash("sha256").update(input, "utf-8").digest("hex");
2002
+ }
2003
+ function findMissingEntries(allow) {
2004
+ const allowSet = new Set(allow);
2005
+ return MASSU_PERMISSION_ENTRIES.filter((entry) => !allowSet.has(entry));
2006
+ }
2007
+ function detectInvalidDefaultMode(settings) {
2008
+ const permissions = settings.permissions;
2009
+ const mode = permissions?.defaultMode;
2010
+ if (typeof mode !== "string") {
2011
+ return { invalid: false };
2012
+ }
2013
+ if (LAUNCH_FLAG_REQUIRED_MODES.includes(mode)) {
2014
+ return {
2015
+ invalid: true,
2016
+ mode,
2017
+ reason: `defaultMode "${mode}" requires --permission-mode launch flag per https://code.claude.com/docs/en/permission-modes \u2014 settings-file value alone is inert. Remediation: launch with --permission-mode ${mode} OR change defaultMode to one of {default, acceptEdits, plan}.`
2018
+ };
2019
+ }
2020
+ return { invalid: false };
2021
+ }
2022
+ function readGlobalSettings() {
2023
+ return readSettingsAtPath(join2(homedir2(), ".claude", "settings.json"));
2024
+ }
2025
+ function mergedPermissionState(global2, local, canonical) {
2026
+ const globalPerm = global2.permissions ?? {};
2027
+ const localPerm = local.permissions ?? {};
2028
+ const localAllow = Array.isArray(localPerm.allow) ? localPerm.allow.filter((e2) => typeof e2 === "string") : [];
2029
+ const allowSet = new Set(localAllow);
2030
+ const allow = [...localAllow];
2031
+ for (const entry of canonical) {
2032
+ if (!allowSet.has(entry)) {
2033
+ allow.push(entry);
2034
+ allowSet.add(entry);
2035
+ }
2036
+ }
2037
+ const result = { allow };
2038
+ if (typeof localPerm.defaultMode === "string") {
2039
+ result.defaultMode = localPerm.defaultMode;
2040
+ } else if (typeof globalPerm.defaultMode === "string") {
2041
+ result.defaultMode = globalPerm.defaultMode;
2042
+ }
2043
+ if (Array.isArray(localPerm.deny)) {
2044
+ result.deny = localPerm.deny.filter((e2) => typeof e2 === "string");
2045
+ }
2046
+ if (Array.isArray(localPerm.ask)) {
2047
+ result.ask = localPerm.ask.filter((e2) => typeof e2 === "string");
2048
+ }
2049
+ return result;
2050
+ }
2051
+ function resolveClaudeDir(claudeDir) {
2052
+ return claudeDir;
2053
+ }
2054
+ function hashOfPermissions(perm) {
2055
+ return sha256Hex(canonicalJson(perm));
2056
+ }
2057
+ function installPermissions(claudeDir, manifest, opts = {}) {
2058
+ const resolvedDir = resolveClaudeDir(claudeDir);
2059
+ const global2 = opts.global ?? readGlobalSettings();
2060
+ const local = readSettingsLocal(resolvedDir);
2061
+ const merged = mergedPermissionState(global2, local, MASSU_PERMISSION_ENTRIES);
2062
+ const expectedHash = hashOfPermissions(merged);
2063
+ const existingPerm = local.permissions ?? void 0;
2064
+ const existingHash = existingPerm ? hashOfPermissions(existingPerm) : void 0;
2065
+ const lastInstalledHash = manifest.entries[MANIFEST_KEY_PERMISSIONS];
2066
+ if (existingHash === expectedHash) {
2067
+ manifest.entries[MANIFEST_KEY_PERMISSIONS] = expectedHash;
2068
+ if (!opts.silent) {
2069
+ process.stderr.write(
2070
+ ` Permissions: already in sync (allow: ${merged.allow.length} entries; defaultMode: ${merged.defaultMode ?? "omitted"}).
2071
+ `
2072
+ );
2073
+ }
2074
+ return { installed: 0, kept: 0, skipped: 1 };
2075
+ }
2076
+ if (lastInstalledHash !== void 0 && existingHash !== lastInstalledHash) {
2077
+ if (!opts.silent) {
2078
+ process.stderr.write(
2079
+ ` Permissions: operator-edited since last install \u2014 preserving. Use \`npx massu permissions check-drift\` to inspect.
2080
+ `
2081
+ );
2082
+ }
2083
+ return { installed: 0, kept: 1, skipped: 0 };
2084
+ }
2085
+ const nextSettings = { ...local, permissions: merged };
2086
+ writeSettingsLocalAtomic(resolvedDir, nextSettings);
2087
+ manifest.entries[MANIFEST_KEY_PERMISSIONS] = expectedHash;
2088
+ const onDisk = readSettingsLocal(resolvedDir);
2089
+ const onDiskPerm = onDisk.permissions ?? {};
2090
+ if (merged.defaultMode !== void 0) {
2091
+ const diskDefaultMode = onDiskPerm.defaultMode;
2092
+ if (diskDefaultMode !== merged.defaultMode) {
2093
+ throw new InstallPermissionsAssertionError(
2094
+ `Post-write assertion failed: expected permissions.defaultMode="${merged.defaultMode}" on disk, got ${JSON.stringify(diskDefaultMode)}. This indicates a filesystem race or write failure.`
2095
+ );
2096
+ }
2097
+ }
2098
+ const diskAllow = Array.isArray(onDiskPerm.allow) ? onDiskPerm.allow : [];
2099
+ for (const entry of MASSU_PERMISSION_ENTRIES) {
2100
+ if (!diskAllow.includes(entry)) {
2101
+ throw new InstallPermissionsAssertionError(
2102
+ `Post-write assertion failed: canonical entry "${entry}" missing from permissions.allow on disk.`
2103
+ );
2104
+ }
2105
+ }
2106
+ if (!opts.silent) {
2107
+ process.stderr.write(
2108
+ ` Wrote merged permissions block to .claude/settings.local.json (allow: ${merged.allow.length} entries; defaultMode: ${merged.defaultMode ?? "omitted"}).
2109
+ `
2110
+ );
2111
+ }
2112
+ return { installed: 1, kept: 0, skipped: 0 };
2113
+ }
2114
+ function verifyPermissions(claudeDir) {
2115
+ const local = readSettingsLocal(claudeDir);
2116
+ const permissions = local.permissions ?? {};
2117
+ const allow = Array.isArray(permissions.allow) ? permissions.allow.filter((e2) => typeof e2 === "string") : [];
2118
+ return {
2119
+ missing: findMissingEntries(allow),
2120
+ allowList: allow
2121
+ };
2122
+ }
2123
+ function checkPermissionsDrift(claudeDir, opts = {}) {
2124
+ const global2 = opts.global ?? readGlobalSettings();
2125
+ const local = readSettingsLocal(claudeDir);
2126
+ const items = [];
2127
+ const localPerm = local.permissions ?? {};
2128
+ const allow = Array.isArray(localPerm.allow) ? localPerm.allow.filter((e2) => typeof e2 === "string") : [];
2129
+ const missing = findMissingEntries(allow);
2130
+ for (const entry of missing) {
2131
+ items.push({
2132
+ kind: "missing-allow",
2133
+ detail: `Canonical massu allowlist entry missing from permissions.allow: "${entry}"`,
2134
+ remediation: "Run `npx massu permissions install` to seed."
2135
+ });
2136
+ }
2137
+ const invalidMode = detectInvalidDefaultMode(local);
2138
+ if (invalidMode.invalid && invalidMode.mode) {
2139
+ items.push({
2140
+ kind: "invalid-default-mode",
2141
+ detail: `defaultMode "${invalidMode.mode}" requires --permission-mode launch flag per code.claude.com/docs/en/permission-modes \u2014 settings-file value alone is inert.`,
2142
+ remediation: `Launch with --permission-mode ${invalidMode.mode} OR change defaultMode to one of {default, acceptEdits, plan}.`
2143
+ });
2144
+ }
2145
+ const globalPerm = global2.permissions ?? {};
2146
+ const localHasPermissions = local.permissions !== void 0 && local.permissions !== null && typeof local.permissions === "object";
2147
+ const localHasDefaultMode = typeof localPerm.defaultMode === "string";
2148
+ const globalHasDefaultMode = typeof globalPerm.defaultMode === "string";
2149
+ if (localHasPermissions && !localHasDefaultMode && globalHasDefaultMode) {
2150
+ items.push({
2151
+ kind: "strips-global-defaultmode",
2152
+ detail: `Project-local permissions object omits defaultMode while global ~/.claude/settings.json has defaultMode="${globalPerm.defaultMode}". Per empirical observation (2026-05-14) the merge unit is the entire permissions object, so the global defaultMode is silently stripped.`,
2153
+ remediation: "Run `npx massu permissions install` to write the full merged permissions block (auto-propagates global defaultMode)."
2154
+ });
2155
+ }
2156
+ for (const key of Object.keys(local)) {
2157
+ if (KNOWN_UNKNOWN_KEYS.has(key)) {
2158
+ items.push({
2159
+ kind: "unknown-key",
2160
+ detail: `Top-level settings key "${key}" is not documented at code.claude.com/docs/en/settings \u2014 silently ignored by Claude Code.`,
2161
+ remediation: `Remove or replace with a documented key (e.g. skipDangerousModePermissionPrompt).`
2162
+ });
2163
+ }
2164
+ }
2165
+ return { driftItems: items };
2166
+ }
2167
+ var MASSU_PERMISSION_ENTRIES, LAUNCH_FLAG_REQUIRED_MODES, InstallPermissionsAssertionError, MANIFEST_KEY_PERMISSIONS, KNOWN_UNKNOWN_KEYS;
2168
+ var init_permissions = __esm({
2169
+ "src/permissions.ts"() {
2170
+ "use strict";
2171
+ init_config();
2172
+ init_settings_local();
2173
+ MASSU_PERMISSION_ENTRIES = ["mcp__massu__*"];
2174
+ LAUNCH_FLAG_REQUIRED_MODES = ["bypassPermissions", "auto", "dontAsk"];
2175
+ InstallPermissionsAssertionError = class extends Error {
2176
+ constructor(message) {
2177
+ super(message);
2178
+ this.name = "InstallPermissionsAssertionError";
2179
+ }
2180
+ };
2181
+ MANIFEST_KEY_PERMISSIONS = "__settings__/permissions";
2182
+ KNOWN_UNKNOWN_KEYS = /* @__PURE__ */ new Set([
2183
+ // From operator's observed ~/.claude/settings.json: top-level key that is
2184
+ // NOT in the docs (looks like a typo/wishful-thinking of skipDangerousModePermissionPrompt).
2185
+ "skipAutoPermissionPrompt"
2186
+ ]);
2187
+ }
2188
+ });
2189
+
1913
2190
  // src/commands/install-commands.ts
1914
2191
  var install_commands_exports = {};
1915
2192
  __export(install_commands_exports, {
@@ -1927,33 +2204,27 @@ __export(install_commands_exports, {
1927
2204
  syncDirectory: () => syncDirectory
1928
2205
  });
1929
2206
  import {
1930
- closeSync,
1931
- existsSync as existsSync4,
1932
- fsyncSync,
1933
- openSync,
1934
- readFileSync as readFileSync3,
1935
- rmSync,
1936
- writeSync,
1937
- mkdirSync as mkdirSync2,
2207
+ existsSync as existsSync5,
2208
+ readFileSync as readFileSync4,
2209
+ mkdirSync as mkdirSync3,
1938
2210
  readdirSync as readdirSync2,
1939
- statSync,
1940
- renameSync
2211
+ statSync
1941
2212
  } from "fs";
1942
- import { resolve as resolve3, dirname as dirname3, join as join2 } from "path";
2213
+ import { resolve as resolve4, dirname as dirname4, join as join3 } from "path";
1943
2214
  import { fileURLToPath } from "url";
1944
- import { createHash } from "crypto";
2215
+ import { createHash as createHash2 } from "crypto";
1945
2216
  function resolveAssetDir(assetName) {
1946
2217
  const cwd = process.cwd();
1947
- const nodeModulesPath = resolve3(cwd, "node_modules/@massu/core", assetName);
1948
- if (existsSync4(nodeModulesPath)) {
2218
+ const nodeModulesPath = resolve4(cwd, "node_modules/@massu/core", assetName);
2219
+ if (existsSync5(nodeModulesPath)) {
1949
2220
  return nodeModulesPath;
1950
2221
  }
1951
- const distRelPath = resolve3(__dirname, "..", assetName);
1952
- if (existsSync4(distRelPath)) {
2222
+ const distRelPath = resolve4(__dirname, "..", assetName);
2223
+ if (existsSync5(distRelPath)) {
1953
2224
  return distRelPath;
1954
2225
  }
1955
- const srcRelPath = resolve3(__dirname, "../..", assetName);
1956
- if (existsSync4(srcRelPath)) {
2226
+ const srcRelPath = resolve4(__dirname, "../..", assetName);
2227
+ if (existsSync5(srcRelPath)) {
1957
2228
  return srcRelPath;
1958
2229
  }
1959
2230
  return null;
@@ -1962,15 +2233,15 @@ function resolveCommandsDir() {
1962
2233
  return resolveAssetDir("commands");
1963
2234
  }
1964
2235
  function hashContent(content) {
1965
- return createHash("sha256").update(content, "utf-8").digest("hex");
2236
+ return createHash2("sha256").update(content, "utf-8").digest("hex");
1966
2237
  }
1967
2238
  function loadManifest(claudeDir) {
1968
- const path = resolve3(claudeDir, MANIFEST_RELPATH);
1969
- if (!existsSync4(path)) {
2239
+ const path = resolve4(claudeDir, MANIFEST_RELPATH);
2240
+ if (!existsSync5(path)) {
1970
2241
  return emptyManifest();
1971
2242
  }
1972
2243
  try {
1973
- const raw = readFileSync3(path, "utf-8");
2244
+ const raw = readFileSync4(path, "utf-8");
1974
2245
  const parsed = JSON.parse(raw);
1975
2246
  if (!parsed || typeof parsed !== "object" || !parsed.entries) {
1976
2247
  return emptyManifest();
@@ -1980,34 +2251,12 @@ function loadManifest(claudeDir) {
1980
2251
  return emptyManifest();
1981
2252
  }
1982
2253
  }
1983
- function atomicWriteFile(targetPath, content, mode = 420) {
1984
- const tmpPath = `${targetPath}.${process.pid}.tmp`;
1985
- try {
1986
- const fd = openSync(tmpPath, "w", mode);
1987
- try {
1988
- const buf = Buffer.from(content, "utf-8");
1989
- writeSync(fd, buf, 0, buf.length, 0);
1990
- fsyncSync(fd);
1991
- } finally {
1992
- closeSync(fd);
1993
- }
1994
- renameSync(tmpPath, targetPath);
1995
- } catch (err) {
1996
- if (existsSync4(tmpPath)) {
1997
- try {
1998
- rmSync(tmpPath, { force: true });
1999
- } catch {
2000
- }
2001
- }
2002
- throw err;
2003
- }
2004
- }
2005
2254
  function saveManifest(claudeDir, manifest) {
2006
- const dir = resolve3(claudeDir, ".massu");
2007
- if (!existsSync4(dir)) {
2008
- mkdirSync2(dir, { recursive: true });
2255
+ const dir = resolve4(claudeDir, ".massu");
2256
+ if (!existsSync5(dir)) {
2257
+ mkdirSync3(dir, { recursive: true });
2009
2258
  }
2010
- const finalPath = resolve3(dir, "install-manifest.json");
2259
+ const finalPath = resolve4(dir, "install-manifest.json");
2011
2260
  manifest.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
2012
2261
  atomicWriteFile(finalPath, JSON.stringify(manifest, null, 2));
2013
2262
  }
@@ -2069,18 +2318,18 @@ function pickVariant(baseName, sourceDir, framework) {
2069
2318
  }
2070
2319
  for (const cand of candidates) {
2071
2320
  if (cand.subFramework) {
2072
- const subPath = resolve3(sourceDir, `${baseName}.${cand.lang}-${cand.subFramework}.md`);
2073
- if (existsSync4(subPath)) {
2321
+ const subPath = resolve4(sourceDir, `${baseName}.${cand.lang}-${cand.subFramework}.md`);
2322
+ if (existsSync5(subPath)) {
2074
2323
  return { kind: "hit", suffix: `.${cand.lang}-${cand.subFramework}` };
2075
2324
  }
2076
2325
  }
2077
- const langPath = resolve3(sourceDir, `${baseName}.${cand.lang}.md`);
2078
- if (existsSync4(langPath)) {
2326
+ const langPath = resolve4(sourceDir, `${baseName}.${cand.lang}.md`);
2327
+ if (existsSync5(langPath)) {
2079
2328
  return { kind: "hit", suffix: `.${cand.lang}` };
2080
2329
  }
2081
2330
  }
2082
- const defaultPath = resolve3(sourceDir, `${baseName}.md`);
2083
- if (existsSync4(defaultPath)) {
2331
+ const defaultPath = resolve4(sourceDir, `${baseName}.md`);
2332
+ if (existsSync5(defaultPath)) {
2084
2333
  return { kind: "hit", suffix: "" };
2085
2334
  }
2086
2335
  if (framework.type === "multi" && !framework.primary) {
@@ -2096,15 +2345,15 @@ function isVariantFilename(entry) {
2096
2345
  }
2097
2346
  function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPrefix, topLevel = true, templateVars = {}) {
2098
2347
  const stats = { installed: 0, updated: 0, skipped: 0, kept: 0 };
2099
- if (!existsSync4(targetDir)) {
2100
- mkdirSync2(targetDir, { recursive: true });
2348
+ if (!existsSync5(targetDir)) {
2349
+ mkdirSync3(targetDir, { recursive: true });
2101
2350
  }
2102
2351
  const entries = readdirSync2(sourceDir);
2103
2352
  for (const entry of entries) {
2104
- const sourcePath = resolve3(sourceDir, entry);
2353
+ const sourcePath = resolve4(sourceDir, entry);
2105
2354
  const entryStat = statSync(sourcePath);
2106
2355
  if (entryStat.isDirectory()) {
2107
- const subTargetDir = resolve3(targetDir, entry);
2356
+ const subTargetDir = resolve4(targetDir, entry);
2108
2357
  const subPrefix = manifestKeyPrefix === "" ? entry : `${manifestKeyPrefix}/${entry}`;
2109
2358
  const subStats = syncDirectory(
2110
2359
  sourcePath,
@@ -2133,13 +2382,13 @@ function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPre
2133
2382
  const suffix = choice.kind === "hit" ? choice.suffix : "";
2134
2383
  sourceFilename = suffix === "" ? `${baseName}.md` : `${baseName}${suffix}.md`;
2135
2384
  }
2136
- const resolvedSourcePath = resolve3(sourceDir, sourceFilename);
2137
- if (!existsSync4(resolvedSourcePath)) {
2385
+ const resolvedSourcePath = resolve4(sourceDir, sourceFilename);
2386
+ if (!existsSync5(resolvedSourcePath)) {
2138
2387
  continue;
2139
2388
  }
2140
2389
  const targetFilename = topLevel ? `${baseName}.md` : entry;
2141
- const targetPath = resolve3(targetDir, targetFilename);
2142
- const rawContent = readFileSync3(resolvedSourcePath, "utf-8");
2390
+ const targetPath = resolve4(targetDir, targetFilename);
2391
+ const rawContent = readFileSync4(resolvedSourcePath, "utf-8");
2143
2392
  let sourceContent;
2144
2393
  try {
2145
2394
  sourceContent = renderTemplate(rawContent, templateVars);
@@ -2157,8 +2406,8 @@ function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPre
2157
2406
  const sourceHash = hashContent(sourceContent);
2158
2407
  const manifestKey = manifestKeyPrefix === "" ? targetFilename : `${manifestKeyPrefix}/${targetFilename}`;
2159
2408
  const lastInstalledHash = manifest.entries[manifestKey];
2160
- if (existsSync4(targetPath)) {
2161
- const existingContent = readFileSync3(targetPath, "utf-8");
2409
+ if (existsSync5(targetPath)) {
2410
+ const existingContent = readFileSync4(targetPath, "utf-8");
2162
2411
  const existingHash = hashContent(existingContent);
2163
2412
  if (existingHash === sourceHash) {
2164
2413
  manifest.entries[manifestKey] = sourceHash;
@@ -2205,12 +2454,12 @@ function buildTemplateVars() {
2205
2454
  config
2206
2455
  };
2207
2456
  }
2208
- function installCommands(projectRoot) {
2457
+ function installCommands(projectRoot, opts = {}) {
2209
2458
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
2210
- const claudeDir = resolve3(projectRoot, claudeDirName);
2211
- const targetDir = resolve3(claudeDir, "commands");
2212
- if (!existsSync4(targetDir)) {
2213
- mkdirSync2(targetDir, { recursive: true });
2459
+ const claudeDir = resolve4(projectRoot, claudeDirName);
2460
+ const targetDir = resolve4(claudeDir, "commands");
2461
+ if (!existsSync5(targetDir)) {
2462
+ mkdirSync3(targetDir, { recursive: true });
2214
2463
  }
2215
2464
  const sourceDir = resolveAssetDir("commands");
2216
2465
  if (!sourceDir) {
@@ -2220,27 +2469,39 @@ function installCommands(projectRoot) {
2220
2469
  }
2221
2470
  const framework = getConfig().framework;
2222
2471
  const templateVars = buildTemplateVars();
2223
- const stats = runWithManifest(
2224
- claudeDir,
2225
- (manifest) => syncDirectory(sourceDir, targetDir, framework, manifest, "commands", true, templateVars)
2226
- );
2472
+ const stats = runWithManifest(claudeDir, (manifest) => {
2473
+ const syncStats = syncDirectory(
2474
+ sourceDir,
2475
+ targetDir,
2476
+ framework,
2477
+ manifest,
2478
+ "commands",
2479
+ true,
2480
+ templateVars
2481
+ );
2482
+ if (!opts.skipPermissions) {
2483
+ installPermissions(claudeDir, manifest, { silent: true });
2484
+ }
2485
+ return syncStats;
2486
+ });
2227
2487
  return { ...stats, commandsDir: targetDir };
2228
2488
  }
2229
- function installAll(projectRoot) {
2489
+ function installAll(projectRoot, opts = {}) {
2230
2490
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
2231
- const claudeDir = resolve3(projectRoot, claudeDirName);
2491
+ const claudeDir = resolve4(projectRoot, claudeDirName);
2232
2492
  const assets = {};
2233
2493
  let totalInstalled = 0;
2234
2494
  let totalUpdated = 0;
2235
2495
  let totalSkipped = 0;
2236
2496
  let totalKept = 0;
2497
+ let permissionsResult;
2237
2498
  const framework = getConfig().framework;
2238
2499
  const templateVars = buildTemplateVars();
2239
2500
  runWithManifest(claudeDir, (manifest) => {
2240
2501
  for (const assetType of ASSET_TYPES) {
2241
2502
  const sourceDir = resolveAssetDir(assetType.name);
2242
2503
  if (!sourceDir) continue;
2243
- const targetDir = resolve3(claudeDir, assetType.targetSubdir);
2504
+ const targetDir = resolve4(claudeDir, assetType.targetSubdir);
2244
2505
  const stats = syncDirectory(
2245
2506
  sourceDir,
2246
2507
  targetDir,
@@ -2256,6 +2517,9 @@ function installAll(projectRoot) {
2256
2517
  totalSkipped += stats.skipped;
2257
2518
  totalKept += stats.kept;
2258
2519
  }
2520
+ if (!opts.skipPermissions) {
2521
+ permissionsResult = installPermissions(claudeDir, manifest, { silent: true });
2522
+ }
2259
2523
  });
2260
2524
  return {
2261
2525
  assets,
@@ -2263,16 +2527,18 @@ function installAll(projectRoot) {
2263
2527
  totalUpdated,
2264
2528
  totalSkipped,
2265
2529
  totalKept,
2266
- claudeDir
2530
+ claudeDir,
2531
+ permissions: permissionsResult
2267
2532
  };
2268
2533
  }
2269
2534
  async function runInstallCommands() {
2270
2535
  const projectRoot = process.cwd();
2536
+ const skipPermissions = process.argv.slice(2).includes("--skip-permissions");
2271
2537
  console.log("");
2272
2538
  console.log("Massu AI - Install Project Assets");
2273
2539
  console.log("==================================");
2274
2540
  console.log("");
2275
- const result = installAll(projectRoot);
2541
+ const result = installAll(projectRoot, { skipPermissions });
2276
2542
  for (const assetType of ASSET_TYPES) {
2277
2543
  const stats = result.assets[assetType.name];
2278
2544
  if (!stats) {
@@ -2296,6 +2562,19 @@ async function runInstallCommands() {
2296
2562
  ` ${result.totalKept} file(s) had local edits and were preserved (see stderr above).`
2297
2563
  );
2298
2564
  }
2565
+ if (skipPermissions) {
2566
+ console.log(" Permission seeding skipped (--skip-permissions).");
2567
+ } else if (result.permissions) {
2568
+ if (result.permissions.installed > 0) {
2569
+ console.log(
2570
+ ` Wrote merged permissions block to .claude/settings.local.json (use --skip-permissions to opt out).`
2571
+ );
2572
+ } else if (result.permissions.kept > 0) {
2573
+ console.log(
2574
+ ` MCP allowlist entry was edited by operator; preserved. Use \`npx massu permissions check-drift\` to inspect.`
2575
+ );
2576
+ }
2577
+ }
2299
2578
  console.log("");
2300
2579
  console.log(" Restart your Claude Code session to use them.");
2301
2580
  console.log("");
@@ -2306,8 +2585,10 @@ var init_install_commands = __esm({
2306
2585
  "use strict";
2307
2586
  init_config();
2308
2587
  init_template_engine();
2588
+ init_settings_local();
2589
+ init_permissions();
2309
2590
  __filename = fileURLToPath(import.meta.url);
2310
- __dirname = dirname3(__filename);
2591
+ __dirname = dirname4(__filename);
2311
2592
  ASSET_TYPES = [
2312
2593
  { name: "commands", targetSubdir: "commands", description: "slash commands" },
2313
2594
  { name: "agents", targetSubdir: "agents", description: "agent definitions" },
@@ -2316,7 +2597,7 @@ var init_install_commands = __esm({
2316
2597
  { name: "reference", targetSubdir: "reference", description: "reference files" }
2317
2598
  ];
2318
2599
  MANIFEST_VERSION = 1;
2319
- MANIFEST_RELPATH = join2(".massu", "install-manifest.json");
2600
+ MANIFEST_RELPATH = join3(".massu", "install-manifest.json");
2320
2601
  PASSTHROUGH_LANG_KEYS = [
2321
2602
  "typescript",
2322
2603
  "javascript",
@@ -2490,17 +2771,17 @@ var init_manifest_registry = __esm({
2490
2771
  });
2491
2772
 
2492
2773
  // src/detect/package-detector.ts
2493
- import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync2, lstatSync, readdirSync as readdirSync3 } from "fs";
2494
- import { join as join3, relative as relative2 } from "path";
2774
+ import { readFileSync as readFileSync5, existsSync as existsSync6, statSync as statSync2, lstatSync, readdirSync as readdirSync3 } from "fs";
2775
+ import { join as join4, relative as relative2 } from "path";
2495
2776
  import { parse as parseToml } from "smol-toml";
2496
2777
  function safeRead(path) {
2497
2778
  try {
2498
- if (!existsSync5(path)) return null;
2779
+ if (!existsSync6(path)) return null;
2499
2780
  const ls = lstatSync(path);
2500
2781
  if (ls.isSymbolicLink()) return null;
2501
2782
  const st = statSync2(path);
2502
2783
  if (!st.isFile()) return null;
2503
- return readFileSync4(path, "utf-8");
2784
+ return readFileSync5(path, "utf-8");
2504
2785
  } catch {
2505
2786
  return null;
2506
2787
  }
@@ -2531,7 +2812,7 @@ function parsePackageJson(path, directory, root, warnings) {
2531
2812
  const peer = Object.keys(
2532
2813
  pkg.peerDependencies ?? {}
2533
2814
  );
2534
- const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync5(join3(directory, "tsconfig.json"));
2815
+ const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync6(join4(directory, "tsconfig.json"));
2535
2816
  const language = hasTs ? "typescript" : "javascript";
2536
2817
  const scripts = Object.keys(
2537
2818
  pkg.scripts ?? {}
@@ -2957,8 +3238,8 @@ function detectManifestsInDir(dir, root, warnings) {
2957
3238
  let dirEntries = null;
2958
3239
  for (const entry of getManifestRegistry2()) {
2959
3240
  if (!entry.pattern.startsWith("*")) {
2960
- const path = join3(dir, entry.pattern);
2961
- if (!existsSync5(path)) continue;
3241
+ const path = join4(dir, entry.pattern);
3242
+ if (!existsSync6(path)) continue;
2962
3243
  const m3 = entry.parse(path, dir, root, warnings);
2963
3244
  if (m3 !== null) out.push(m3);
2964
3245
  } else {
@@ -2971,8 +3252,8 @@ function detectManifestsInDir(dir, root, warnings) {
2971
3252
  }
2972
3253
  for (const fname of dirEntries) {
2973
3254
  if (!matchManifestPattern2(fname, entry.pattern)) continue;
2974
- const path = join3(dir, fname);
2975
- if (!existsSync5(path)) continue;
3255
+ const path = join4(dir, fname);
3256
+ if (!existsSync6(path)) continue;
2976
3257
  const m3 = entry.parse(path, dir, root, warnings);
2977
3258
  if (m3 !== null) out.push(m3);
2978
3259
  }
@@ -2982,7 +3263,7 @@ function detectManifestsInDir(dir, root, warnings) {
2982
3263
  }
2983
3264
  function listSubdirs(dir) {
2984
3265
  try {
2985
- return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && !IGNORED_DIRS.has(e2.name)).map((e2) => join3(dir, e2.name));
3266
+ return readdirSync3(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && !IGNORED_DIRS.has(e2.name)).map((e2) => join4(dir, e2.name));
2986
3267
  } catch {
2987
3268
  return [];
2988
3269
  }
@@ -2992,8 +3273,8 @@ function detectPackageManifests(projectRoot) {
2992
3273
  const manifests = [];
2993
3274
  manifests.push(...detectManifestsInDir(projectRoot, projectRoot, warnings));
2994
3275
  for (const ws of WORKSPACE_DIRS) {
2995
- const wsRoot = join3(projectRoot, ws);
2996
- if (!existsSync5(wsRoot)) continue;
3276
+ const wsRoot = join4(projectRoot, ws);
3277
+ if (!existsSync6(wsRoot)) continue;
2997
3278
  for (const sub of listSubdirs(wsRoot)) {
2998
3279
  manifests.push(...detectManifestsInDir(sub, projectRoot, warnings));
2999
3280
  for (const sub2 of listSubdirs(sub)) {
@@ -7764,41 +8045,41 @@ var require_queue = __commonJS({
7764
8045
  queue.drained = drained;
7765
8046
  return queue;
7766
8047
  function push(value) {
7767
- var p19 = new Promise(function(resolve36, reject) {
8048
+ var p19 = new Promise(function(resolve38, reject) {
7768
8049
  pushCb(value, function(err, result) {
7769
8050
  if (err) {
7770
8051
  reject(err);
7771
8052
  return;
7772
8053
  }
7773
- resolve36(result);
8054
+ resolve38(result);
7774
8055
  });
7775
8056
  });
7776
8057
  p19.catch(noop);
7777
8058
  return p19;
7778
8059
  }
7779
8060
  function unshift(value) {
7780
- var p19 = new Promise(function(resolve36, reject) {
8061
+ var p19 = new Promise(function(resolve38, reject) {
7781
8062
  unshiftCb(value, function(err, result) {
7782
8063
  if (err) {
7783
8064
  reject(err);
7784
8065
  return;
7785
8066
  }
7786
- resolve36(result);
8067
+ resolve38(result);
7787
8068
  });
7788
8069
  });
7789
8070
  p19.catch(noop);
7790
8071
  return p19;
7791
8072
  }
7792
8073
  function drained() {
7793
- var p19 = new Promise(function(resolve36) {
8074
+ var p19 = new Promise(function(resolve38) {
7794
8075
  process.nextTick(function() {
7795
8076
  if (queue.idle()) {
7796
- resolve36();
8077
+ resolve38();
7797
8078
  } else {
7798
8079
  var previousDrain = queue.drain;
7799
8080
  queue.drain = function() {
7800
8081
  if (typeof previousDrain === "function") previousDrain();
7801
- resolve36();
8082
+ resolve38();
7802
8083
  queue.drain = previousDrain;
7803
8084
  };
7804
8085
  }
@@ -8284,9 +8565,9 @@ var require_stream3 = __commonJS({
8284
8565
  });
8285
8566
  }
8286
8567
  _getStat(filepath) {
8287
- return new Promise((resolve36, reject) => {
8568
+ return new Promise((resolve38, reject) => {
8288
8569
  this._stat(filepath, this._fsStatSettings, (error, stats) => {
8289
- return error === null ? resolve36(stats) : reject(error);
8570
+ return error === null ? resolve38(stats) : reject(error);
8290
8571
  });
8291
8572
  });
8292
8573
  }
@@ -8310,10 +8591,10 @@ var require_async5 = __commonJS({
8310
8591
  this._readerStream = new stream_1.default(this._settings);
8311
8592
  }
8312
8593
  dynamic(root, options) {
8313
- return new Promise((resolve36, reject) => {
8594
+ return new Promise((resolve38, reject) => {
8314
8595
  this._walkAsync(root, options, (error, entries) => {
8315
8596
  if (error === null) {
8316
- resolve36(entries);
8597
+ resolve38(entries);
8317
8598
  } else {
8318
8599
  reject(error);
8319
8600
  }
@@ -8323,10 +8604,10 @@ var require_async5 = __commonJS({
8323
8604
  async static(patterns, options) {
8324
8605
  const entries = [];
8325
8606
  const stream = this._readerStream.static(patterns, options);
8326
- return new Promise((resolve36, reject) => {
8607
+ return new Promise((resolve38, reject) => {
8327
8608
  stream.once("error", reject);
8328
8609
  stream.on("data", (entry) => entries.push(entry));
8329
- stream.once("end", () => resolve36(entries));
8610
+ stream.once("end", () => resolve38(entries));
8330
8611
  });
8331
8612
  }
8332
8613
  };
@@ -8985,7 +9266,7 @@ var require_out4 = __commonJS({
8985
9266
 
8986
9267
  // src/detect/source-dir-detector.ts
8987
9268
  import { realpathSync } from "fs";
8988
- import { resolve as resolve4 } from "path";
9269
+ import { resolve as resolve5 } from "path";
8989
9270
  function extsFor(language) {
8990
9271
  return EXTENSIONS[language] ?? [];
8991
9272
  }
@@ -9011,7 +9292,7 @@ function topSegment(rel) {
9011
9292
  function isInsideRoot(root, candidate) {
9012
9293
  try {
9013
9294
  const realRoot = realpathSync(root);
9014
- const realCand = realpathSync(resolve4(root, candidate));
9295
+ const realCand = realpathSync(resolve5(root, candidate));
9015
9296
  return realCand === realRoot || realCand.startsWith(realRoot + "/");
9016
9297
  } catch {
9017
9298
  return false;
@@ -9179,38 +9460,38 @@ var init_source_dir_detector = __esm({
9179
9460
  });
9180
9461
 
9181
9462
  // src/detect/monorepo-detector.ts
9182
- import { readFileSync as readFileSync5, existsSync as existsSync6, statSync as statSync3, lstatSync as lstatSync2, readdirSync as readdirSync4 } from "fs";
9183
- import { join as join4, relative as relative3 } from "path";
9463
+ import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync3, lstatSync as lstatSync2, readdirSync as readdirSync4 } from "fs";
9464
+ import { join as join5, relative as relative3 } from "path";
9184
9465
  import { parse as parseYaml3 } from "yaml";
9185
9466
  import { parse as parseToml2 } from "smol-toml";
9186
9467
  function safeReadText(path) {
9187
9468
  try {
9188
- if (!existsSync6(path)) return null;
9469
+ if (!existsSync7(path)) return null;
9189
9470
  const ls = lstatSync2(path);
9190
9471
  if (ls.isSymbolicLink()) return null;
9191
9472
  const st = statSync3(path);
9192
9473
  if (!st.isFile()) return null;
9193
- return readFileSync5(path, "utf-8");
9474
+ return readFileSync6(path, "utf-8");
9194
9475
  } catch {
9195
9476
  return null;
9196
9477
  }
9197
9478
  }
9198
9479
  function firstManifestIn(dir) {
9199
9480
  for (const m3 of MANIFEST_PRIORITY) {
9200
- if (existsSync6(join4(dir, m3))) return m3;
9481
+ if (existsSync7(join5(dir, m3))) return m3;
9201
9482
  }
9202
9483
  return null;
9203
9484
  }
9204
9485
  function manifestName(dir, manifest) {
9205
9486
  try {
9206
9487
  if (manifest === "package.json") {
9207
- const raw = safeReadText(join4(dir, "package.json"));
9488
+ const raw = safeReadText(join5(dir, "package.json"));
9208
9489
  if (!raw) return null;
9209
9490
  const pkg = JSON.parse(raw);
9210
9491
  return typeof pkg.name === "string" ? pkg.name : null;
9211
9492
  }
9212
9493
  if (manifest === "pyproject.toml") {
9213
- const raw = safeReadText(join4(dir, "pyproject.toml"));
9494
+ const raw = safeReadText(join5(dir, "pyproject.toml"));
9214
9495
  if (!raw) return null;
9215
9496
  const toml = parseToml2(raw);
9216
9497
  const project = toml.project;
@@ -9221,7 +9502,7 @@ function manifestName(dir, manifest) {
9221
9502
  return null;
9222
9503
  }
9223
9504
  if (manifest === "Cargo.toml") {
9224
- const raw = safeReadText(join4(dir, "Cargo.toml"));
9505
+ const raw = safeReadText(join5(dir, "Cargo.toml"));
9225
9506
  if (!raw) return null;
9226
9507
  const toml = parseToml2(raw);
9227
9508
  const pkg = toml.package;
@@ -9229,7 +9510,7 @@ function manifestName(dir, manifest) {
9229
9510
  return null;
9230
9511
  }
9231
9512
  if (manifest === "go.mod") {
9232
- const raw = safeReadText(join4(dir, "go.mod"));
9513
+ const raw = safeReadText(join5(dir, "go.mod"));
9233
9514
  if (!raw) return null;
9234
9515
  for (const line of raw.split(/\r?\n/)) {
9235
9516
  const trimmed = line.trim();
@@ -9253,7 +9534,7 @@ function pkgFromDir(root, dir) {
9253
9534
  }
9254
9535
  function listSubdirs2(dir) {
9255
9536
  try {
9256
- return readdirSync4(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && !IGNORED_DIRS2.has(e2.name)).map((e2) => join4(dir, e2.name));
9537
+ return readdirSync4(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && !IGNORED_DIRS2.has(e2.name)).map((e2) => join5(dir, e2.name));
9257
9538
  } catch {
9258
9539
  return [];
9259
9540
  }
@@ -9261,8 +9542,8 @@ function listSubdirs2(dir) {
9261
9542
  function genericWorkspaces(root) {
9262
9543
  const out = [];
9263
9544
  for (const parent of CONVENTIONAL_WORKSPACE_PARENTS) {
9264
- const p19 = join4(root, parent);
9265
- if (!existsSync6(p19)) continue;
9545
+ const p19 = join5(root, parent);
9546
+ if (!existsSync7(p19)) continue;
9266
9547
  for (const sub of listSubdirs2(p19)) {
9267
9548
  const pkg = pkgFromDir(root, sub);
9268
9549
  if (pkg) out.push(pkg);
@@ -9271,7 +9552,7 @@ function genericWorkspaces(root) {
9271
9552
  return out;
9272
9553
  }
9273
9554
  function detectYarnWorkspaces(root) {
9274
- const raw = safeReadText(join4(root, "package.json"));
9555
+ const raw = safeReadText(join5(root, "package.json"));
9275
9556
  if (!raw) return null;
9276
9557
  let pkg;
9277
9558
  try {
@@ -9286,7 +9567,7 @@ function detectYarnWorkspaces(root) {
9286
9567
  return expandWorkspaceGlobs(root, globs);
9287
9568
  }
9288
9569
  function detectPnpmWorkspaces(root) {
9289
- const raw = safeReadText(join4(root, "pnpm-workspace.yaml"));
9570
+ const raw = safeReadText(join5(root, "pnpm-workspace.yaml"));
9290
9571
  if (!raw) return null;
9291
9572
  try {
9292
9573
  const parsed = parseYaml3(raw);
@@ -9302,8 +9583,8 @@ function expandWorkspaceGlobs(root, globs) {
9302
9583
  for (const pattern of globs) {
9303
9584
  const parts = pattern.split("/");
9304
9585
  if (parts.length === 2 && (parts[1] === "*" || parts[1] === "**")) {
9305
- const parent = join4(root, parts[0]);
9306
- if (!existsSync6(parent)) continue;
9586
+ const parent = join5(root, parts[0]);
9587
+ if (!existsSync7(parent)) continue;
9307
9588
  for (const sub of listSubdirs2(parent)) {
9308
9589
  const pkg = pkgFromDir(root, sub);
9309
9590
  if (pkg && !seen.has(pkg.path)) {
@@ -9313,8 +9594,8 @@ function expandWorkspaceGlobs(root, globs) {
9313
9594
  }
9314
9595
  continue;
9315
9596
  }
9316
- const direct = join4(root, pattern);
9317
- if (existsSync6(direct)) {
9597
+ const direct = join5(root, pattern);
9598
+ if (existsSync7(direct)) {
9318
9599
  const pkg = pkgFromDir(root, direct);
9319
9600
  if (pkg && !seen.has(pkg.path)) {
9320
9601
  seen.add(pkg.path);
@@ -9325,16 +9606,16 @@ function expandWorkspaceGlobs(root, globs) {
9325
9606
  return out;
9326
9607
  }
9327
9608
  function hasTurbo(root) {
9328
- return existsSync6(join4(root, "turbo.json"));
9609
+ return existsSync7(join5(root, "turbo.json"));
9329
9610
  }
9330
9611
  function hasNx(root) {
9331
- return existsSync6(join4(root, "nx.json"));
9612
+ return existsSync7(join5(root, "nx.json"));
9332
9613
  }
9333
9614
  function hasLerna(root) {
9334
- return existsSync6(join4(root, "lerna.json"));
9615
+ return existsSync7(join5(root, "lerna.json"));
9335
9616
  }
9336
9617
  function hasBazel(root) {
9337
- return existsSync6(join4(root, "WORKSPACE")) || existsSync6(join4(root, "WORKSPACE.bazel")) || existsSync6(join4(root, "MODULE.bazel"));
9618
+ return existsSync7(join5(root, "WORKSPACE")) || existsSync7(join5(root, "WORKSPACE.bazel")) || existsSync7(join5(root, "MODULE.bazel"));
9338
9619
  }
9339
9620
  function detectMonorepo(projectRoot) {
9340
9621
  const nested = [];
@@ -9525,8 +9806,8 @@ var init_vr_command_map = __esm({
9525
9806
  });
9526
9807
 
9527
9808
  // src/detect/domain-inferrer.ts
9528
- import { existsSync as existsSync7, readdirSync as readdirSync5 } from "fs";
9529
- import { join as join5 } from "path";
9809
+ import { existsSync as existsSync8, readdirSync as readdirSync5 } from "fs";
9810
+ import { join as join6 } from "path";
9530
9811
  function titleCase(s) {
9531
9812
  if (!s) return s;
9532
9813
  return s.split(/[-_\s]+/).filter(Boolean).map((p19) => p19.charAt(0).toUpperCase() + p19.slice(1)).join(" ");
@@ -9546,8 +9827,8 @@ function topLevelSrcSubdirs(root, sourceDirs) {
9546
9827
  const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
9547
9828
  const seen = /* @__PURE__ */ new Set();
9548
9829
  for (const rel of effective) {
9549
- const abs = join5(root, rel);
9550
- if (!existsSync7(abs)) continue;
9830
+ const abs = join6(root, rel);
9831
+ if (!existsSync8(abs)) continue;
9551
9832
  try {
9552
9833
  for (const e2 of readdirSync5(abs, { withFileTypes: true })) {
9553
9834
  if (!e2.isDirectory()) continue;
@@ -9634,8 +9915,8 @@ var init_domain_inferrer = __esm({
9634
9915
  });
9635
9916
 
9636
9917
  // src/detect/regex-fallback.ts
9637
- import { existsSync as existsSync8, readdirSync as readdirSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
9638
- import { resolve as resolve5, join as join6, basename as basename2 } from "path";
9918
+ import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
9919
+ import { resolve as resolve6, join as join7, basename as basename2 } from "path";
9639
9920
  function introspectPython(detection, projectRoot) {
9640
9921
  const sourceDir = resolveSourceDir(detection, "python", projectRoot);
9641
9922
  if (!sourceDir) return null;
@@ -9801,10 +10082,10 @@ function resolveSourceDir(detection, lang, projectRoot) {
9801
10082
  const list = info?.source_dirs ?? [];
9802
10083
  if (list.length > 0) {
9803
10084
  const first = list[0];
9804
- const abs = resolve5(projectRoot, first);
9805
- return existsSync8(abs) ? abs : null;
10085
+ const abs = resolve6(projectRoot, first);
10086
+ return existsSync9(abs) ? abs : null;
9806
10087
  }
9807
- return existsSync8(projectRoot) ? projectRoot : null;
10088
+ return existsSync9(projectRoot) ? projectRoot : null;
9808
10089
  }
9809
10090
  function sampleFiles(dir, nameRegex, pathFilter) {
9810
10091
  const out = [];
@@ -9824,7 +10105,7 @@ function sampleFiles(dir, nameRegex, pathFilter) {
9824
10105
  if (entry === "__pycache__") continue;
9825
10106
  if (entry === "venv" || entry === ".venv") continue;
9826
10107
  if (entry === "dist" || entry === "build") continue;
9827
- const child = join6(path, entry);
10108
+ const child = join7(path, entry);
9828
10109
  let st;
9829
10110
  try {
9830
10111
  st = statSync4(child);
@@ -9849,7 +10130,7 @@ function readSafe(path) {
9849
10130
  try {
9850
10131
  const st = statSync4(path);
9851
10132
  if (st.size > MAX_FILE_BYTES) return null;
9852
- return readFileSync6(path, "utf-8");
10133
+ return readFileSync7(path, "utf-8");
9853
10134
  } catch {
9854
10135
  return null;
9855
10136
  }
@@ -9930,8 +10211,8 @@ var init_parse_guard = __esm({
9930
10211
 
9931
10212
  // src/detect/adapters/runner.ts
9932
10213
  import { basename as basename3, relative as relative4 } from "path";
9933
- import { existsSync as existsSync9, readdirSync as readdirSync7, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
9934
- import { join as join7 } from "path";
10214
+ import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
10215
+ import { join as join8 } from "path";
9935
10216
  async function runAdapters(adapters, rootDir, signals, options = {}) {
9936
10217
  const out = {
9937
10218
  byAdapter: {},
@@ -10028,7 +10309,7 @@ function buildDetectionSignals(rootDir) {
10028
10309
  for (const entry of readdirSync7(rootDir)) {
10029
10310
  if (entry.startsWith(".")) continue;
10030
10311
  try {
10031
- const st = statSync5(join7(rootDir, entry));
10312
+ const st = statSync5(join8(rootDir, entry));
10032
10313
  if (st.isDirectory()) presentDirs.add(entry);
10033
10314
  else if (st.isFile()) presentFiles.add(entry);
10034
10315
  } catch {
@@ -10037,14 +10318,14 @@ function buildDetectionSignals(rootDir) {
10037
10318
  } catch {
10038
10319
  }
10039
10320
  return {
10040
- packageJson: tryReadJson(join7(rootDir, "package.json")),
10041
- pyprojectToml: tryReadToml(join7(rootDir, "pyproject.toml")),
10042
- gemfile: tryReadString(join7(rootDir, "Gemfile")),
10043
- cargoToml: tryReadToml(join7(rootDir, "Cargo.toml")),
10044
- goMod: tryReadString(join7(rootDir, "go.mod")),
10045
- mixExs: tryReadString(join7(rootDir, "mix.exs")),
10321
+ packageJson: tryReadJson(join8(rootDir, "package.json")),
10322
+ pyprojectToml: tryReadToml(join8(rootDir, "pyproject.toml")),
10323
+ gemfile: tryReadString(join8(rootDir, "Gemfile")),
10324
+ cargoToml: tryReadToml(join8(rootDir, "Cargo.toml")),
10325
+ goMod: tryReadString(join8(rootDir, "go.mod")),
10326
+ mixExs: tryReadString(join8(rootDir, "mix.exs")),
10046
10327
  csproj: tryReadFirstCsproj(rootDir, presentFiles),
10047
- pomXml: tryReadString(join7(rootDir, "pom.xml")),
10328
+ pomXml: tryReadString(join8(rootDir, "pom.xml")),
10048
10329
  gradleBuild: tryReadGradleBuild(rootDir, presentFiles),
10049
10330
  presentDirs,
10050
10331
  presentFiles
@@ -10053,21 +10334,21 @@ function buildDetectionSignals(rootDir) {
10053
10334
  function tryReadFirstCsproj(rootDir, presentFiles) {
10054
10335
  const csprojNames = [...presentFiles].filter((f2) => f2.endsWith(".csproj")).sort();
10055
10336
  if (csprojNames.length === 0) return void 0;
10056
- return tryReadString(join7(rootDir, csprojNames[0]));
10337
+ return tryReadString(join8(rootDir, csprojNames[0]));
10057
10338
  }
10058
10339
  function tryReadGradleBuild(rootDir, presentFiles) {
10059
10340
  if (presentFiles.has("build.gradle.kts")) {
10060
- return tryReadString(join7(rootDir, "build.gradle.kts"));
10341
+ return tryReadString(join8(rootDir, "build.gradle.kts"));
10061
10342
  }
10062
10343
  if (presentFiles.has("build.gradle")) {
10063
- return tryReadString(join7(rootDir, "build.gradle"));
10344
+ return tryReadString(join8(rootDir, "build.gradle"));
10064
10345
  }
10065
10346
  return void 0;
10066
10347
  }
10067
10348
  function tryReadString(path) {
10068
- if (!existsSync9(path)) return void 0;
10349
+ if (!existsSync10(path)) return void 0;
10069
10350
  try {
10070
- return readFileSync7(path, "utf-8");
10351
+ return readFileSync8(path, "utf-8");
10071
10352
  } catch {
10072
10353
  return void 0;
10073
10354
  }
@@ -10182,11 +10463,11 @@ ${querySource}`
10182
10463
  });
10183
10464
 
10184
10465
  // src/detect/adapters/tree-sitter-loader.ts
10185
- import { createHash as createHash2 } from "crypto";
10466
+ import { createHash as createHash3 } from "crypto";
10186
10467
  import {
10187
- mkdirSync as mkdirSync3,
10468
+ mkdirSync as mkdirSync4,
10188
10469
  readdirSync as readdirSync8,
10189
- readFileSync as readFileSync8,
10470
+ readFileSync as readFileSync9,
10190
10471
  writeFileSync,
10191
10472
  renameSync as renameSync2,
10192
10473
  unlinkSync,
@@ -10194,14 +10475,14 @@ import {
10194
10475
  chmodSync,
10195
10476
  utimesSync
10196
10477
  } from "fs";
10197
- import { homedir as homedir2 } from "os";
10198
- import { dirname as dirname4, join as join8 } from "path";
10478
+ import { homedir as homedir3 } from "os";
10479
+ import { dirname as dirname5, join as join9 } from "path";
10199
10480
  import { Language, Parser } from "web-tree-sitter";
10200
10481
  function getCacheDir() {
10201
- return process.env.MASSU_WASM_CACHE_DIR ?? join8(homedir2(), ".massu", "wasm-cache");
10482
+ return process.env.MASSU_WASM_CACHE_DIR ?? join9(homedir3(), ".massu", "wasm-cache");
10202
10483
  }
10203
10484
  function getCachedPath(language, sha) {
10204
- return join8(getCacheDir(), `${language}-${sha}.wasm`);
10485
+ return join9(getCacheDir(), `${language}-${sha}.wasm`);
10205
10486
  }
10206
10487
  function getCacheRetainCount() {
10207
10488
  const env = process.env.MASSU_WASM_CACHE_RETAIN;
@@ -10229,7 +10510,7 @@ function evictBeyondRetainCount(retain = getCacheRetainCount()) {
10229
10510
  const candidates = [];
10230
10511
  for (const name of entries) {
10231
10512
  if (!name.endsWith(".wasm")) continue;
10232
- const path = join8(dir, name);
10513
+ const path = join9(dir, name);
10233
10514
  let stat;
10234
10515
  try {
10235
10516
  stat = lstatSync3(path);
@@ -10254,7 +10535,7 @@ function evictBeyondRetainCount(retain = getCacheRetainCount()) {
10254
10535
  }
10255
10536
  }
10256
10537
  function sha256(bytes) {
10257
- return createHash2("sha256").update(bytes).digest("hex");
10538
+ return createHash3("sha256").update(bytes).digest("hex");
10258
10539
  }
10259
10540
  async function ensureParserInitialized() {
10260
10541
  if (parserInitPromise) return parserInitPromise;
@@ -10285,7 +10566,7 @@ async function loadGrammar(language, options = {}) {
10285
10566
  }
10286
10567
  let bytes;
10287
10568
  try {
10288
- bytes = readFileSync8(cachePath);
10569
+ bytes = readFileSync9(cachePath);
10289
10570
  } catch (e2) {
10290
10571
  bytes = new Uint8Array(0);
10291
10572
  }
@@ -10325,9 +10606,9 @@ async function loadGrammar(language, options = {}) {
10325
10606
  throw new GrammarSHAMismatchError(language, manifest.sha256, downloadedSha);
10326
10607
  }
10327
10608
  try {
10328
- mkdirSync3(dirname4(cachePath), { recursive: true, mode: 448 });
10609
+ mkdirSync4(dirname5(cachePath), { recursive: true, mode: 448 });
10329
10610
  try {
10330
- chmodSync(dirname4(cachePath), 448);
10611
+ chmodSync(dirname5(cachePath), 448);
10331
10612
  } catch {
10332
10613
  }
10333
10614
  const tmpPath = `${cachePath}.tmp.${process.pid}`;
@@ -11226,8 +11507,8 @@ __export(file_sampler_exports, {
11226
11507
  SAMPLE_TEST_FILE_PATTERNS: () => SAMPLE_TEST_FILE_PATTERNS,
11227
11508
  sampleFilesForAdapter: () => sampleFilesForAdapter
11228
11509
  });
11229
- import { readdirSync as readdirSync9, readFileSync as readFileSync9, lstatSync as lstatSync4 } from "node:fs";
11230
- import { join as join9, extname } from "node:path";
11510
+ import { readdirSync as readdirSync9, readFileSync as readFileSync10, lstatSync as lstatSync4 } from "node:fs";
11511
+ import { join as join10, extname } from "node:path";
11231
11512
  function sampleFilesForAdapter(adapter, projectRoot, detection, options = {}) {
11232
11513
  const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
11233
11514
  const maxFiles = options.maxFilesPerAdapter ?? DEFAULT_MAX_FILES;
@@ -11242,7 +11523,7 @@ function sampleFilesForAdapter(adapter, projectRoot, detection, options = {}) {
11242
11523
  const langDetection = detection.sourceDirs[langKey];
11243
11524
  const candidateDirs = [];
11244
11525
  if (langDetection?.source_dirs && langDetection.source_dirs.length > 0) {
11245
- candidateDirs.push(...langDetection.source_dirs.map((d2) => join9(projectRoot, d2)));
11526
+ candidateDirs.push(...langDetection.source_dirs.map((d2) => join10(projectRoot, d2)));
11246
11527
  } else {
11247
11528
  candidateDirs.push(projectRoot);
11248
11529
  }
@@ -11266,7 +11547,7 @@ function walkDir(dir, exts, testPatterns, lang, maxDepth, curDepth, out, seen, m
11266
11547
  if (out.length >= maxFiles) return;
11267
11548
  if (entry.startsWith(".")) continue;
11268
11549
  if (IGNORED_DIRS3.has(entry)) continue;
11269
- const fullPath = join9(dir, entry);
11550
+ const fullPath = join10(dir, entry);
11270
11551
  let st;
11271
11552
  try {
11272
11553
  st = lstatSync4(fullPath);
@@ -11287,7 +11568,7 @@ function walkDir(dir, exts, testPatterns, lang, maxDepth, curDepth, out, seen, m
11287
11568
  seen.add(fullPath);
11288
11569
  let content;
11289
11570
  try {
11290
- content = readFileSync9(fullPath, "utf-8");
11571
+ content = readFileSync10(fullPath, "utf-8");
11291
11572
  } catch {
11292
11573
  continue;
11293
11574
  }
@@ -11525,7 +11806,7 @@ var init_detect = __esm({
11525
11806
  });
11526
11807
 
11527
11808
  // src/detect/drift.ts
11528
- import { createHash as createHash3 } from "crypto";
11809
+ import { createHash as createHash4 } from "crypto";
11529
11810
  function summarizeDetection(det) {
11530
11811
  const languages = Array.from(new Set(det.manifests.map((m3) => m3.language))).sort();
11531
11812
  const frameworks = {};
@@ -11556,7 +11837,7 @@ function summarizeDetection(det) {
11556
11837
  function computeFingerprint(det) {
11557
11838
  const data = summarizeDetection(det);
11558
11839
  const stable = JSON.stringify(data, Object.keys(data).sort());
11559
- return createHash3("sha256").update(stable).digest("hex");
11840
+ return createHash4("sha256").update(stable).digest("hex");
11560
11841
  }
11561
11842
  function stringOf(v3) {
11562
11843
  if (typeof v3 === "string") return v3;
@@ -12715,10 +12996,10 @@ __export(init_exports, {
12715
12996
  validateWrittenConfig: () => validateWrittenConfig,
12716
12997
  writeConfigAtomic: () => writeConfigAtomic
12717
12998
  });
12718
- import { closeSync as closeSync2, existsSync as existsSync10, fsyncSync as fsyncSync2, openSync as openSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync2, writeSync as writeSync2, mkdirSync as mkdirSync4, readdirSync as readdirSync10, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync7, chmodSync as chmodSync2 } from "fs";
12719
- import { resolve as resolve6, basename as basename4, dirname as dirname5 } from "path";
12999
+ import { closeSync as closeSync2, existsSync as existsSync11, fsyncSync as fsyncSync2, openSync as openSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync2, writeSync as writeSync2, mkdirSync as mkdirSync5, readdirSync as readdirSync10, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync7, chmodSync as chmodSync2 } from "fs";
13000
+ import { resolve as resolve7, basename as basename4, dirname as dirname6 } from "path";
12720
13001
  import { fileURLToPath as fileURLToPath2 } from "url";
12721
- import { homedir as homedir3 } from "os";
13002
+ import { homedir as homedir4 } from "os";
12722
13003
  import { stringify as yamlStringify, parse as yamlParse } from "yaml";
12723
13004
  function detectFramework(projectRoot) {
12724
13005
  const result = {
@@ -12727,10 +13008,10 @@ function detectFramework(projectRoot) {
12727
13008
  orm: "none",
12728
13009
  ui: "none"
12729
13010
  };
12730
- const pkgPath = resolve6(projectRoot, "package.json");
12731
- if (!existsSync10(pkgPath)) return result;
13011
+ const pkgPath = resolve7(projectRoot, "package.json");
13012
+ if (!existsSync11(pkgPath)) return result;
12732
13013
  try {
12733
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
13014
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
12734
13015
  const allDeps = {
12735
13016
  ...pkg.dependencies,
12736
13017
  ...pkg.devDependencies
@@ -12764,7 +13045,7 @@ function detectPython(projectRoot) {
12764
13045
  alembicDir: null
12765
13046
  };
12766
13047
  const markers = ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"];
12767
- const hasMarker = markers.some((m3) => existsSync10(resolve6(projectRoot, m3)));
13048
+ const hasMarker = markers.some((m3) => existsSync11(resolve7(projectRoot, m3)));
12768
13049
  if (!hasMarker) return result;
12769
13050
  result.detected = true;
12770
13051
  const depFiles = [
@@ -12774,33 +13055,33 @@ function detectPython(projectRoot) {
12774
13055
  { file: "Pipfile" }
12775
13056
  ];
12776
13057
  for (const { file } of depFiles) {
12777
- const filePath = resolve6(projectRoot, file);
12778
- if (existsSync10(filePath)) {
13058
+ const filePath = resolve7(projectRoot, file);
13059
+ if (existsSync11(filePath)) {
12779
13060
  try {
12780
- const content = readFileSync10(filePath, "utf-8").toLowerCase();
13061
+ const content = readFileSync11(filePath, "utf-8").toLowerCase();
12781
13062
  if (content.includes("fastapi")) result.hasFastapi = true;
12782
13063
  if (content.includes("sqlalchemy")) result.hasSqlalchemy = true;
12783
13064
  } catch {
12784
13065
  }
12785
13066
  }
12786
13067
  }
12787
- if (existsSync10(resolve6(projectRoot, "alembic.ini"))) {
13068
+ if (existsSync11(resolve7(projectRoot, "alembic.ini"))) {
12788
13069
  result.hasAlembic = true;
12789
- if (existsSync10(resolve6(projectRoot, "alembic"))) {
13070
+ if (existsSync11(resolve7(projectRoot, "alembic"))) {
12790
13071
  result.alembicDir = "alembic";
12791
13072
  }
12792
- } else if (existsSync10(resolve6(projectRoot, "alembic"))) {
13073
+ } else if (existsSync11(resolve7(projectRoot, "alembic"))) {
12793
13074
  result.hasAlembic = true;
12794
13075
  result.alembicDir = "alembic";
12795
13076
  }
12796
13077
  const candidateRoots = ["app", "src", "backend", "api"];
12797
13078
  for (const candidate of candidateRoots) {
12798
- const candidatePath = resolve6(projectRoot, candidate);
12799
- if (existsSync10(candidatePath) && existsSync10(resolve6(candidatePath, "__init__.py"))) {
13079
+ const candidatePath = resolve7(projectRoot, candidate);
13080
+ if (existsSync11(candidatePath) && existsSync11(resolve7(candidatePath, "__init__.py"))) {
12800
13081
  result.root = candidate;
12801
13082
  break;
12802
13083
  }
12803
- if (existsSync10(candidatePath)) {
13084
+ if (existsSync11(candidatePath)) {
12804
13085
  try {
12805
13086
  const files = readdirSync10(candidatePath);
12806
13087
  if (files.some((f2) => f2.endsWith(".py"))) {
@@ -12820,8 +13101,8 @@ function generateConfig(projectRoot, framework) {
12820
13101
  console.warn(
12821
13102
  "[@massu/core] generateConfig() is deprecated since 1.2.1 \u2014 use buildConfigFromDetection instead. It cannot produce valid configs for monorepos."
12822
13103
  );
12823
- const configPath = resolve6(projectRoot, "massu.config.yaml");
12824
- if (existsSync10(configPath)) {
13104
+ const configPath = resolve7(projectRoot, "massu.config.yaml");
13105
+ if (existsSync11(configPath)) {
12825
13106
  return false;
12826
13107
  }
12827
13108
  const projectName = basename4(projectRoot);
@@ -13010,7 +13291,7 @@ function buildConfigFromDetection(opts) {
13010
13291
  };
13011
13292
  if (pyFw?.framework) pythonBlock.framework = pyFw.framework;
13012
13293
  if (pyFw?.orm) pythonBlock.orm = pyFw.orm;
13013
- if (existsSync10(resolve6(projectRoot, "alembic.ini")) || existsSync10(resolve6(projectRoot, "alembic"))) {
13294
+ if (existsSync11(resolve7(projectRoot, "alembic.ini")) || existsSync11(resolve7(projectRoot, "alembic"))) {
13014
13295
  pythonBlock.alembic_dir = "alembic";
13015
13296
  }
13016
13297
  config.python = pythonBlock;
@@ -13034,11 +13315,11 @@ function applyVariantTemplate(config, templatesDir) {
13034
13315
  }
13035
13316
  }
13036
13317
  if (templateId === null) return config;
13037
- const templatePath = resolve6(templatesDir, templateId, "massu.config.yaml");
13038
- if (!existsSync10(templatePath)) return config;
13318
+ const templatePath = resolve7(templatesDir, templateId, "massu.config.yaml");
13319
+ if (!existsSync11(templatePath)) return config;
13039
13320
  let template;
13040
13321
  try {
13041
- template = yamlParse(readFileSync10(templatePath, "utf-8"));
13322
+ template = yamlParse(readFileSync11(templatePath, "utf-8"));
13042
13323
  } catch {
13043
13324
  return config;
13044
13325
  }
@@ -13097,7 +13378,7 @@ ${yamlStringify(config)}`;
13097
13378
  function writeConfigAtomic(configPath, content) {
13098
13379
  const tmpPath = `${configPath}.tmp`;
13099
13380
  let existingMode;
13100
- if (existsSync10(configPath)) {
13381
+ if (existsSync11(configPath)) {
13101
13382
  try {
13102
13383
  existingMode = statSync7(configPath).mode;
13103
13384
  } catch {
@@ -13105,7 +13386,7 @@ function writeConfigAtomic(configPath, content) {
13105
13386
  }
13106
13387
  }
13107
13388
  try {
13108
- mkdirSync4(dirname5(configPath), { recursive: true });
13389
+ mkdirSync5(dirname6(configPath), { recursive: true });
13109
13390
  const fd = openSync2(tmpPath, "w", 420);
13110
13391
  try {
13111
13392
  const buf = Buffer.from(content, "utf-8");
@@ -13127,7 +13408,7 @@ function writeConfigAtomic(configPath, content) {
13127
13408
  }
13128
13409
  return { validated: true };
13129
13410
  } catch (err) {
13130
- if (existsSync10(tmpPath)) {
13411
+ if (existsSync11(tmpPath)) {
13131
13412
  try {
13132
13413
  rmSync2(tmpPath, { force: true });
13133
13414
  } catch {
@@ -13138,8 +13419,8 @@ function writeConfigAtomic(configPath, content) {
13138
13419
  }
13139
13420
  function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
13140
13421
  try {
13141
- if (!existsSync10(configPath)) return "Config file does not exist after write";
13142
- const content = readFileSync10(configPath, "utf-8");
13422
+ if (!existsSync11(configPath)) return "Config file does not exist after write";
13423
+ const content = readFileSync11(configPath, "utf-8");
13143
13424
  const parsed = yamlParse(content);
13144
13425
  if (parsed === null || typeof parsed !== "object") {
13145
13426
  return "Config is not a valid YAML object";
@@ -13159,8 +13440,8 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
13159
13440
  if (checkPaths) {
13160
13441
  const src = cfg.paths.source;
13161
13442
  if (src && src !== ".") {
13162
- const srcAbs = resolve6(projectRoot, src);
13163
- if (!existsSync10(srcAbs)) {
13443
+ const srcAbs = resolve7(projectRoot, src);
13444
+ if (!existsSync11(srcAbs)) {
13164
13445
  return `paths.source '${src}' does not exist on disk`;
13165
13446
  }
13166
13447
  }
@@ -13170,8 +13451,8 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
13170
13451
  if (!Array.isArray(rawDirs)) continue;
13171
13452
  for (const d2 of rawDirs) {
13172
13453
  if (typeof d2 !== "string" || d2 === ".") continue;
13173
- const abs = resolve6(projectRoot, d2);
13174
- if (!existsSync10(abs)) {
13454
+ const abs = resolve7(projectRoot, d2);
13455
+ if (!existsSync11(abs)) {
13175
13456
  return `framework.languages.${lang}.source_dirs '${d2}' does not exist on disk`;
13176
13457
  }
13177
13458
  }
@@ -13180,7 +13461,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
13180
13461
  if (Array.isArray(mRoots)) {
13181
13462
  for (const r2 of mRoots) {
13182
13463
  if (typeof r2 !== "string" || r2 === ".") continue;
13183
- if (!existsSync10(resolve6(projectRoot, r2))) {
13464
+ if (!existsSync11(resolve7(projectRoot, r2))) {
13184
13465
  return `paths.monorepo_roots '${r2}' does not exist on disk`;
13185
13466
  }
13186
13467
  }
@@ -13212,21 +13493,21 @@ function resolveTemplatesDir() {
13212
13493
  const cwd = process.cwd();
13213
13494
  const candidates = [
13214
13495
  // Project-local install: `<project>/node_modules/@massu/core/templates`.
13215
- resolve6(cwd, "node_modules/@massu/core/templates"),
13496
+ resolve7(cwd, "node_modules/@massu/core/templates"),
13216
13497
  // Bundled cli.js layout: cli.js sits at `<package>/dist/cli.js`, so
13217
13498
  // templates live one level up at `<package>/templates`. (Plan 1.5.1
13218
13499
  // bug discovery: pre-existing layout assumed `dist/commands/init.js`
13219
13500
  // depth which never matched the bundled cli, so resolveTemplatesDir
13220
13501
  // returned null in production for both `--template` mode AND the
13221
13502
  // applyVariantTemplate path.)
13222
- resolve6(__dirname2, "../templates"),
13503
+ resolve7(__dirname2, "../templates"),
13223
13504
  // Legacy nested layouts retained as fallbacks (in case a future
13224
13505
  // build moves cli.js back into a subdirectory).
13225
- resolve6(__dirname2, "../../templates"),
13226
- resolve6(__dirname2, "../../../templates")
13506
+ resolve7(__dirname2, "../../templates"),
13507
+ resolve7(__dirname2, "../../../templates")
13227
13508
  ];
13228
13509
  for (const c2 of candidates) {
13229
- if (existsSync10(c2)) return c2;
13510
+ if (existsSync11(c2)) return c2;
13230
13511
  }
13231
13512
  return null;
13232
13513
  }
@@ -13235,12 +13516,12 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
13235
13516
  if (!templatesDir) {
13236
13517
  return { success: false, error: `Templates directory not found (looked in node_modules and dist/src)` };
13237
13518
  }
13238
- const srcPath = resolve6(templatesDir, templateName, "massu.config.yaml");
13239
- if (!existsSync10(srcPath)) {
13519
+ const srcPath = resolve7(templatesDir, templateName, "massu.config.yaml");
13520
+ if (!existsSync11(srcPath)) {
13240
13521
  return { success: false, error: `Template '${templateName}' not found at ${srcPath}` };
13241
13522
  }
13242
13523
  try {
13243
- let content = readFileSync10(srcPath, "utf-8");
13524
+ let content = readFileSync11(srcPath, "utf-8");
13244
13525
  content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
13245
13526
  writeFileSync2(targetPath, content, "utf-8");
13246
13527
  return { success: true };
@@ -13249,11 +13530,11 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
13249
13530
  }
13250
13531
  }
13251
13532
  function registerMcpServer(projectRoot) {
13252
- const mcpPath = resolve6(projectRoot, ".mcp.json");
13533
+ const mcpPath = resolve7(projectRoot, ".mcp.json");
13253
13534
  let existing = {};
13254
- if (existsSync10(mcpPath)) {
13535
+ if (existsSync11(mcpPath)) {
13255
13536
  try {
13256
- existing = JSON.parse(readFileSync10(mcpPath, "utf-8"));
13537
+ existing = JSON.parse(readFileSync11(mcpPath, "utf-8"));
13257
13538
  } catch {
13258
13539
  existing = {};
13259
13540
  }
@@ -13273,12 +13554,12 @@ function registerMcpServer(projectRoot) {
13273
13554
  }
13274
13555
  function resolveHooksDir() {
13275
13556
  const cwd = process.cwd();
13276
- const nodeModulesPath = resolve6(cwd, "node_modules/@massu/core/dist/hooks");
13277
- if (existsSync10(nodeModulesPath)) {
13557
+ const nodeModulesPath = resolve7(cwd, "node_modules/@massu/core/dist/hooks");
13558
+ if (existsSync11(nodeModulesPath)) {
13278
13559
  return "node_modules/@massu/core/dist/hooks";
13279
13560
  }
13280
- const localPath = resolve6(__dirname2, "../dist/hooks");
13281
- if (existsSync10(localPath)) {
13561
+ const localPath = resolve7(__dirname2, "../dist/hooks");
13562
+ if (existsSync11(localPath)) {
13282
13563
  return localPath;
13283
13564
  }
13284
13565
  return "node_modules/@massu/core/dist/hooks";
@@ -13370,19 +13651,11 @@ function installHooks(projectRoot) {
13370
13651
  } catch {
13371
13652
  claudeDirName = ".claude";
13372
13653
  }
13373
- const claudeDir = resolve6(projectRoot, claudeDirName);
13374
- const settingsPath = resolve6(claudeDir, "settings.local.json");
13375
- if (!existsSync10(claudeDir)) {
13376
- mkdirSync4(claudeDir, { recursive: true });
13377
- }
13378
- let settings = {};
13379
- if (existsSync10(settingsPath)) {
13380
- try {
13381
- settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
13382
- } catch {
13383
- settings = {};
13384
- }
13654
+ const claudeDir = resolve7(projectRoot, claudeDirName);
13655
+ if (!existsSync11(claudeDir)) {
13656
+ mkdirSync5(claudeDir, { recursive: true });
13385
13657
  }
13658
+ const settings = readSettingsLocal(claudeDir);
13386
13659
  const hooksDir = resolveHooksDir();
13387
13660
  const hooksConfig = buildHooksConfig(hooksDir);
13388
13661
  let hookCount = 0;
@@ -13392,20 +13665,20 @@ function installHooks(projectRoot) {
13392
13665
  }
13393
13666
  }
13394
13667
  settings.hooks = hooksConfig;
13395
- writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
13668
+ writeSettingsLocalAtomic(claudeDir, settings);
13396
13669
  return { installed: true, count: hookCount };
13397
13670
  }
13398
13671
  function initMemoryDir(projectRoot) {
13399
13672
  const encodedRoot = "-" + projectRoot.replace(/\//g, "-");
13400
- const memoryDir = resolve6(homedir3(), `.claude/projects/${encodedRoot}/memory`);
13673
+ const memoryDir = resolve7(homedir4(), `.claude/projects/${encodedRoot}/memory`);
13401
13674
  let created = false;
13402
- if (!existsSync10(memoryDir)) {
13403
- mkdirSync4(memoryDir, { recursive: true });
13675
+ if (!existsSync11(memoryDir)) {
13676
+ mkdirSync5(memoryDir, { recursive: true });
13404
13677
  created = true;
13405
13678
  }
13406
- const memoryMdPath = resolve6(memoryDir, "MEMORY.md");
13679
+ const memoryMdPath = resolve7(memoryDir, "MEMORY.md");
13407
13680
  let memoryMdCreated = false;
13408
- if (!existsSync10(memoryMdPath)) {
13681
+ if (!existsSync11(memoryMdPath)) {
13409
13682
  const projectName = basename4(projectRoot);
13410
13683
  const memoryContent = `# ${projectName} - Massu Memory
13411
13684
 
@@ -13511,8 +13784,8 @@ async function runInit(argv, overrides) {
13511
13784
  log("Massu AI - Project Setup");
13512
13785
  log("========================");
13513
13786
  log("");
13514
- const configPath = resolve6(projectRoot, "massu.config.yaml");
13515
- if (existsSync10(configPath)) {
13787
+ const configPath = resolve7(projectRoot, "massu.config.yaml");
13788
+ if (existsSync11(configPath)) {
13516
13789
  if (opts.ci && !opts.force) {
13517
13790
  errLog(`error: massu.config.yaml already exists at ${configPath}`);
13518
13791
  errLog(" rerun with --force to overwrite, or remove the file first");
@@ -13651,8 +13924,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
13651
13924
  const commandStats = cmdResult.assets.commands;
13652
13925
  const stackResolved = !emptyStack && commandStats && (commandStats.installed > 0 || commandStats.updated > 0 || commandStats.kept > 0);
13653
13926
  if (!stackResolved) {
13654
- const placeholderPath = resolve6(cmdResult.claudeDir, "commands", "_massu-needs-stack.md");
13655
- if (!existsSync10(placeholderPath)) {
13927
+ const placeholderPath = resolve7(cmdResult.claudeDir, "commands", "_massu-needs-stack.md");
13928
+ if (!existsSync11(placeholderPath)) {
13656
13929
  const placeholderBody = [
13657
13930
  "# Massu \u2014 stack not yet detected",
13658
13931
  "",
@@ -13673,7 +13946,7 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
13673
13946
  "\u2014 Massu"
13674
13947
  ].join("\n");
13675
13948
  try {
13676
- mkdirSync4(resolve6(cmdResult.claudeDir, "commands"), { recursive: true });
13949
+ mkdirSync5(resolve7(cmdResult.claudeDir, "commands"), { recursive: true });
13677
13950
  writeFileSync2(placeholderPath, placeholderBody, "utf-8");
13678
13951
  log(" Wrote _massu-needs-stack.md placeholder (no stack detected yet)");
13679
13952
  } catch {
@@ -13693,8 +13966,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
13693
13966
  (async () => {
13694
13967
  try {
13695
13968
  const encodedRoot = projectRoot.replace(/\//g, "-");
13696
- const memoryDir = resolve6(homedir3(), ".claude", "projects", encodedRoot, "memory");
13697
- const memFiles = existsSync10(memoryDir) ? readdirSync10(memoryDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md") : [];
13969
+ const memoryDir = resolve7(homedir4(), ".claude", "projects", encodedRoot, "memory");
13970
+ const memFiles = existsSync11(memoryDir) ? readdirSync10(memoryDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md") : [];
13698
13971
  if (memFiles.length > 0) {
13699
13972
  const { getMemoryDb: getMemoryDb2 } = await Promise.resolve().then(() => (init_memory_db(), memory_db_exports));
13700
13973
  const db = getMemoryDb2();
@@ -13751,10 +14024,11 @@ var init_init = __esm({
13751
14024
  init_memory_file_ingest();
13752
14025
  init_config();
13753
14026
  init_install_commands();
14027
+ init_settings_local();
13754
14028
  init_detect();
13755
14029
  init_drift();
13756
14030
  __filename2 = fileURLToPath2(import.meta.url);
13757
- __dirname2 = dirname5(__filename2);
14031
+ __dirname2 = dirname6(__filename2);
13758
14032
  FRAMEWORK_TO_TEMPLATE_ID = {
13759
14033
  rails: "rails",
13760
14034
  phoenix: "phoenix",
@@ -13776,7 +14050,7 @@ var init_init = __esm({
13776
14050
  });
13777
14051
 
13778
14052
  // src/license.ts
13779
- import { createHash as createHash4 } from "crypto";
14053
+ import { createHash as createHash5 } from "crypto";
13780
14054
  function tierLevel(tier) {
13781
14055
  return TIER_LEVELS[tier] ?? 0;
13782
14056
  }
@@ -13804,7 +14078,7 @@ function isCloudFeatureAvailable() {
13804
14078
  return getConfig().cloud?.enabled === true;
13805
14079
  }
13806
14080
  async function validateLicense(apiKey) {
13807
- const keyHash = createHash4("sha256").update(apiKey).digest("hex");
14081
+ const keyHash = createHash5("sha256").update(apiKey).digest("hex");
13808
14082
  const memDb = getMemoryDb();
13809
14083
  try {
13810
14084
  const cached = memDb.prepare(
@@ -13865,7 +14139,7 @@ async function validateLicense(apiKey) {
13865
14139
  }
13866
14140
  }
13867
14141
  function updateLicenseCache(apiKey, tier, validUntil, features = []) {
13868
- const keyHash = createHash4("sha256").update(apiKey).digest("hex");
14142
+ const keyHash = createHash5("sha256").update(apiKey).digest("hex");
13869
14143
  const memDb = getMemoryDb();
13870
14144
  try {
13871
14145
  memDb.prepare(`
@@ -14073,17 +14347,17 @@ __export(doctor_exports, {
14073
14347
  runDoctor: () => runDoctor,
14074
14348
  runValidateConfig: () => runValidateConfig
14075
14349
  });
14076
- import { existsSync as existsSync11, readFileSync as readFileSync11, readdirSync as readdirSync11 } from "fs";
14077
- import { resolve as resolve7, dirname as dirname6 } from "path";
14350
+ import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync11 } from "fs";
14351
+ import { resolve as resolve8, dirname as dirname7 } from "path";
14078
14352
  import { fileURLToPath as fileURLToPath3 } from "url";
14079
14353
  import { parse as parseYaml4 } from "yaml";
14080
14354
  function checkConfig(projectRoot) {
14081
- const configPath = resolve7(projectRoot, "massu.config.yaml");
14082
- if (!existsSync11(configPath)) {
14355
+ const configPath = resolve8(projectRoot, "massu.config.yaml");
14356
+ if (!existsSync12(configPath)) {
14083
14357
  return { name: "Configuration", status: "fail", detail: "massu.config.yaml not found. Run: npx massu init" };
14084
14358
  }
14085
14359
  try {
14086
- const content = readFileSync11(configPath, "utf-8");
14360
+ const content = readFileSync12(configPath, "utf-8");
14087
14361
  const parsed = parseYaml4(content);
14088
14362
  if (!parsed || typeof parsed !== "object") {
14089
14363
  return { name: "Configuration", status: "fail", detail: "massu.config.yaml is empty or invalid YAML" };
@@ -14095,11 +14369,11 @@ function checkConfig(projectRoot) {
14095
14369
  }
14096
14370
  function checkMcpServer(projectRoot) {
14097
14371
  const mcpPath = getResolvedPaths().mcpJsonPath;
14098
- if (!existsSync11(mcpPath)) {
14372
+ if (!existsSync12(mcpPath)) {
14099
14373
  return { name: "MCP Server", status: "fail", detail: ".mcp.json not found. Run: npx massu init" };
14100
14374
  }
14101
14375
  try {
14102
- const content = JSON.parse(readFileSync11(mcpPath, "utf-8"));
14376
+ const content = JSON.parse(readFileSync12(mcpPath, "utf-8"));
14103
14377
  const servers = content.mcpServers ?? {};
14104
14378
  if (!servers.massu) {
14105
14379
  return { name: "MCP Server", status: "fail", detail: "massu not registered in .mcp.json. Run: npx massu init" };
@@ -14111,11 +14385,11 @@ function checkMcpServer(projectRoot) {
14111
14385
  }
14112
14386
  function checkHooksConfig(projectRoot) {
14113
14387
  const settingsPath = getResolvedPaths().settingsLocalPath;
14114
- if (!existsSync11(settingsPath)) {
14388
+ if (!existsSync12(settingsPath)) {
14115
14389
  return { name: "Hooks Config", status: "fail", detail: ".claude/settings.local.json not found. Run: npx massu init" };
14116
14390
  }
14117
14391
  try {
14118
- const content = JSON.parse(readFileSync11(settingsPath, "utf-8"));
14392
+ const content = readSettingsAtPath(settingsPath);
14119
14393
  if (!content.hooks) {
14120
14394
  return { name: "Hooks Config", status: "fail", detail: "No hooks configured. Run: npx massu install-hooks" };
14121
14395
  }
@@ -14139,11 +14413,11 @@ function checkHooksConfig(projectRoot) {
14139
14413
  }
14140
14414
  }
14141
14415
  function checkHookFiles(projectRoot) {
14142
- const nodeModulesHooksDir = resolve7(projectRoot, "node_modules/@massu/core/dist/hooks");
14416
+ const nodeModulesHooksDir = resolve8(projectRoot, "node_modules/@massu/core/dist/hooks");
14143
14417
  let hooksDir = nodeModulesHooksDir;
14144
- if (!existsSync11(nodeModulesHooksDir)) {
14145
- const devHooksDir = resolve7(__dirname3, "../../dist/hooks");
14146
- if (existsSync11(devHooksDir)) {
14418
+ if (!existsSync12(nodeModulesHooksDir)) {
14419
+ const devHooksDir = resolve8(__dirname3, "../../dist/hooks");
14420
+ if (existsSync12(devHooksDir)) {
14147
14421
  hooksDir = devHooksDir;
14148
14422
  } else {
14149
14423
  return { name: "Hook Files", status: "fail", detail: "Compiled hooks not found. Run: npm install @massu/core" };
@@ -14151,7 +14425,7 @@ function checkHookFiles(projectRoot) {
14151
14425
  }
14152
14426
  const missing = [];
14153
14427
  for (const hookFile of EXPECTED_HOOKS) {
14154
- if (!existsSync11(resolve7(hooksDir, hookFile))) {
14428
+ if (!existsSync12(resolve8(hooksDir, hookFile))) {
14155
14429
  missing.push(hookFile);
14156
14430
  }
14157
14431
  }
@@ -14177,8 +14451,8 @@ function checkNodeVersion() {
14177
14451
  return { name: "Node.js", status: "fail", detail: `v${version} \u2014 Node.js 18+ is required` };
14178
14452
  }
14179
14453
  async function checkGitRepo(projectRoot) {
14180
- const gitDir = resolve7(projectRoot, ".git");
14181
- if (!existsSync11(gitDir)) {
14454
+ const gitDir = resolve8(projectRoot, ".git");
14455
+ if (!existsSync12(gitDir)) {
14182
14456
  return { name: "Git Repository", status: "warn", detail: "Not a git repository (optional but recommended)" };
14183
14457
  }
14184
14458
  try {
@@ -14196,7 +14470,7 @@ async function checkGitRepo(projectRoot) {
14196
14470
  }
14197
14471
  function checkKnowledgeDb(projectRoot) {
14198
14472
  const knowledgeDbPath = getResolvedPaths().memoryDbPath;
14199
- if (!existsSync11(knowledgeDbPath)) {
14473
+ if (!existsSync12(knowledgeDbPath)) {
14200
14474
  return {
14201
14475
  name: "Knowledge DB",
14202
14476
  status: "warn",
@@ -14207,7 +14481,7 @@ function checkKnowledgeDb(projectRoot) {
14207
14481
  }
14208
14482
  function checkMemoryDir(_projectRoot2) {
14209
14483
  const memoryDir = getResolvedPaths().memoryDir;
14210
- if (!existsSync11(memoryDir)) {
14484
+ if (!existsSync12(memoryDir)) {
14211
14485
  return {
14212
14486
  name: "Memory Directory",
14213
14487
  status: "warn",
@@ -14218,7 +14492,7 @@ function checkMemoryDir(_projectRoot2) {
14218
14492
  }
14219
14493
  function checkShellHooksWired(_projectRoot2) {
14220
14494
  const settingsPath = getResolvedPaths().settingsLocalPath;
14221
- if (!existsSync11(settingsPath)) {
14495
+ if (!existsSync12(settingsPath)) {
14222
14496
  return {
14223
14497
  name: "Shell Hooks",
14224
14498
  status: "fail",
@@ -14226,7 +14500,7 @@ function checkShellHooksWired(_projectRoot2) {
14226
14500
  };
14227
14501
  }
14228
14502
  try {
14229
- const content = JSON.parse(readFileSync11(settingsPath, "utf-8"));
14503
+ const content = readSettingsAtPath(settingsPath);
14230
14504
  const hooks = content.hooks ?? {};
14231
14505
  const hasSessionStart = Array.isArray(hooks.SessionStart) && hooks.SessionStart.length > 0;
14232
14506
  const hasPreToolUse = Array.isArray(hooks.PreToolUse) && hooks.PreToolUse.length > 0;
@@ -14277,8 +14551,8 @@ async function checkLicenseStatus() {
14277
14551
  function checkPythonHealth(projectRoot) {
14278
14552
  const config = getConfig();
14279
14553
  if (!config.python?.root) return null;
14280
- const pythonRoot = resolve7(projectRoot, config.python.root);
14281
- if (!existsSync11(pythonRoot)) {
14554
+ const pythonRoot = resolve8(projectRoot, config.python.root);
14555
+ if (!existsSync12(pythonRoot)) {
14282
14556
  return {
14283
14557
  name: "Python",
14284
14558
  status: "fail",
@@ -14297,8 +14571,8 @@ function checkPythonHealth(projectRoot) {
14297
14571
  if (entry.isDirectory()) {
14298
14572
  const excludeDirs = config.python?.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
14299
14573
  if (!excludeDirs.includes(entry.name)) {
14300
- const subdir = resolve7(dir, entry.name);
14301
- if (depth <= 2 && !existsSync11(resolve7(subdir, "__init__.py"))) {
14574
+ const subdir = resolve8(dir, entry.name);
14575
+ if (depth <= 2 && !existsSync12(resolve8(subdir, "__init__.py"))) {
14302
14576
  try {
14303
14577
  const subEntries = readdirSync11(subdir);
14304
14578
  if (subEntries.some((f2) => f2.endsWith(".py") && f2 !== "__init__.py")) {
@@ -14393,15 +14667,15 @@ async function runDoctor() {
14393
14667
  }
14394
14668
  async function runValidateConfig() {
14395
14669
  const projectRoot = process.cwd();
14396
- const configPath = resolve7(projectRoot, "massu.config.yaml");
14397
- if (!existsSync11(configPath)) {
14670
+ const configPath = resolve8(projectRoot, "massu.config.yaml");
14671
+ if (!existsSync12(configPath)) {
14398
14672
  console.error("Error: massu.config.yaml not found in current directory");
14399
14673
  console.error("Run: npx massu init");
14400
14674
  process.exit(1);
14401
14675
  return;
14402
14676
  }
14403
14677
  try {
14404
- const content = readFileSync11(configPath, "utf-8");
14678
+ const content = readFileSync12(configPath, "utf-8");
14405
14679
  const parsed = parseYaml4(content);
14406
14680
  if (!parsed || typeof parsed !== "object") {
14407
14681
  console.error("Error: massu.config.yaml is empty or not a valid YAML object");
@@ -14439,8 +14713,9 @@ var init_doctor = __esm({
14439
14713
  "use strict";
14440
14714
  init_config();
14441
14715
  init_license();
14716
+ init_settings_local();
14442
14717
  __filename3 = fileURLToPath3(import.meta.url);
14443
- __dirname3 = dirname6(__filename3);
14718
+ __dirname3 = dirname7(__filename3);
14444
14719
  EXPECTED_HOOKS = [
14445
14720
  "session-start.js",
14446
14721
  "session-end.js",
@@ -14481,13 +14756,140 @@ var init_install_hooks = __esm({
14481
14756
  }
14482
14757
  });
14483
14758
 
14759
+ // src/commands/permissions.ts
14760
+ var permissions_exports = {};
14761
+ __export(permissions_exports, {
14762
+ handlePermissionsSubcommand: () => handlePermissionsSubcommand,
14763
+ printPermissionsHelp: () => printPermissionsHelp
14764
+ });
14765
+ import { resolve as resolve9 } from "path";
14766
+ function resolveClaudeDir2() {
14767
+ let claudeDirName = ".claude";
14768
+ try {
14769
+ claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
14770
+ } catch {
14771
+ claudeDirName = ".claude";
14772
+ }
14773
+ return resolve9(process.cwd(), claudeDirName);
14774
+ }
14775
+ async function handlePermissionsSubcommand(args2) {
14776
+ const sub = args2[0];
14777
+ switch (sub) {
14778
+ case "install": {
14779
+ const claudeDir = resolveClaudeDir2();
14780
+ const result = runWithManifest(
14781
+ claudeDir,
14782
+ (manifest) => installPermissions(claudeDir, manifest, { silent: false })
14783
+ );
14784
+ if (result.installed > 0) {
14785
+ process.stdout.write(
14786
+ "Wrote merged permissions block to .claude/settings.local.json.\n"
14787
+ );
14788
+ } else if (result.skipped > 0) {
14789
+ process.stdout.write("Permissions already in sync \u2014 no changes.\n");
14790
+ } else if (result.kept > 0) {
14791
+ process.stdout.write(
14792
+ "Operator-edited permissions block preserved. Run `npx massu permissions check-drift` to inspect.\n"
14793
+ );
14794
+ }
14795
+ return { exitCode: 0 };
14796
+ }
14797
+ case "verify": {
14798
+ const claudeDir = resolveClaudeDir2();
14799
+ const { missing } = verifyPermissions(claudeDir);
14800
+ if (missing.length === 0) {
14801
+ process.stdout.write("All MCP allowlist entries present.\n");
14802
+ return { exitCode: 0 };
14803
+ }
14804
+ for (const entry of missing) {
14805
+ process.stderr.write(`missing: ${entry}
14806
+ `);
14807
+ }
14808
+ return { exitCode: 1 };
14809
+ }
14810
+ case "check-drift": {
14811
+ const claudeDir = resolveClaudeDir2();
14812
+ const { driftItems } = checkPermissionsDrift(claudeDir);
14813
+ if (driftItems.length === 0) {
14814
+ process.stdout.write("No permission drift detected.\n");
14815
+ return { exitCode: 0 };
14816
+ }
14817
+ let highest = 0;
14818
+ for (const item of driftItems) {
14819
+ const code = DRIFT_KIND_EXIT_CODE[item.kind];
14820
+ if (code > highest) highest = code;
14821
+ process.stderr.write(
14822
+ `drift[${item.kind}]: ${item.detail} \u2014 remediation: ${item.remediation}
14823
+ `
14824
+ );
14825
+ }
14826
+ return { exitCode: highest };
14827
+ }
14828
+ case "--help":
14829
+ case "-h":
14830
+ case void 0: {
14831
+ printPermissionsHelp();
14832
+ return { exitCode: 0 };
14833
+ }
14834
+ default: {
14835
+ process.stderr.write(`massu: unknown permissions subcommand: ${sub}
14836
+ `);
14837
+ printPermissionsHelp();
14838
+ return { exitCode: 1 };
14839
+ }
14840
+ }
14841
+ }
14842
+ function printPermissionsHelp() {
14843
+ process.stdout.write(`
14844
+ massu permissions <subcommand>
14845
+
14846
+ Subcommands:
14847
+ install Seed mcp__massu__* into .claude/settings.local.json's permissions.allow.
14848
+ Also propagates global defaultMode (from ~/.claude/settings.json) into
14849
+ the project-local file to prevent the merge-replacement trap (see
14850
+ https://massu.ai/docs/reference/cli-reference#permissions-trap).
14851
+ Idempotent. Preserves operator-edited values.
14852
+
14853
+ verify Read-only check that all canonical MCP allowlist entries are present.
14854
+ Exit 0 if clean, exit 1 with one diagnostic line per missing entry.
14855
+
14856
+ check-drift Extended diagnostic surfacing 4 drift kinds:
14857
+ - missing-allow (exit 1) \u2014 canonical entries missing
14858
+ - invalid-default-mode (exit 2) \u2014 defaultMode requires launch flag
14859
+ - unknown-key (exit 3) \u2014 undocumented top-level setting
14860
+ - strips-global-defaultmode (exit 4) \u2014 project-local would strip global value
14861
+
14862
+ Examples:
14863
+ npx massu permissions install
14864
+ npx massu permissions verify
14865
+ npx massu permissions check-drift
14866
+
14867
+ Documentation: https://massu.ai/docs/reference/cli-reference#massu-permissions
14868
+ `);
14869
+ }
14870
+ var DRIFT_KIND_EXIT_CODE;
14871
+ var init_permissions2 = __esm({
14872
+ "src/commands/permissions.ts"() {
14873
+ "use strict";
14874
+ init_config();
14875
+ init_permissions();
14876
+ init_install_commands();
14877
+ DRIFT_KIND_EXIT_CODE = {
14878
+ "missing-allow": 1,
14879
+ "invalid-default-mode": 2,
14880
+ "unknown-key": 3,
14881
+ "strips-global-defaultmode": 4
14882
+ };
14883
+ }
14884
+ });
14885
+
14484
14886
  // src/commands/show-template.ts
14485
14887
  var show_template_exports = {};
14486
14888
  __export(show_template_exports, {
14487
14889
  runShowTemplate: () => runShowTemplate
14488
14890
  });
14489
- import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
14490
- import { resolve as resolve8 } from "path";
14891
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
14892
+ import { resolve as resolve10 } from "path";
14491
14893
  function normalizeBaseName(input) {
14492
14894
  return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
14493
14895
  }
@@ -14515,14 +14917,14 @@ async function runShowTemplate(args2) {
14515
14917
  return;
14516
14918
  }
14517
14919
  const suffix = choice.kind === "hit" ? choice.suffix : "";
14518
- const file = suffix === "" ? resolve8(sourceDir, `${baseName}.md`) : resolve8(sourceDir, `${baseName}${suffix}.md`);
14519
- if (!existsSync12(file)) {
14920
+ const file = suffix === "" ? resolve10(sourceDir, `${baseName}.md`) : resolve10(sourceDir, `${baseName}${suffix}.md`);
14921
+ if (!existsSync13(file)) {
14520
14922
  process.stderr.write(`massu: resolved template "${file}" no longer exists
14521
14923
  `);
14522
14924
  process.exit(1);
14523
14925
  return;
14524
14926
  }
14525
- process.stdout.write(readFileSync12(file, "utf-8"));
14927
+ process.stdout.write(readFileSync13(file, "utf-8"));
14526
14928
  }
14527
14929
  var init_show_template = __esm({
14528
14930
  "src/commands/show-template.ts"() {
@@ -14575,12 +14977,12 @@ var init_passthrough = __esm({
14575
14977
  });
14576
14978
 
14577
14979
  // src/lib/fileLock.ts
14578
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync13, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
14579
- import { dirname as dirname7 } from "path";
14980
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
14981
+ import { dirname as dirname8 } from "path";
14580
14982
  import * as lockfile from "proper-lockfile";
14581
14983
  function readLockHolderPid(lockPath) {
14582
14984
  try {
14583
- const raw = readFileSync13(`${lockPath}.pid`, "utf-8").trim();
14985
+ const raw = readFileSync14(`${lockPath}.pid`, "utf-8").trim();
14584
14986
  const pid = Number.parseInt(raw, 10);
14585
14987
  if (!Number.isFinite(pid) || pid <= 0) return null;
14586
14988
  return pid;
@@ -14599,7 +15001,7 @@ function busyWaitSync(ms) {
14599
15001
  Atomics.wait(view, 0, 0, ms);
14600
15002
  }
14601
15003
  function withFileLockSync(lockPath, fn, opts = {}) {
14602
- mkdirSync5(dirname7(lockPath), { recursive: true });
15004
+ mkdirSync6(dirname8(lockPath), { recursive: true });
14603
15005
  const staleMs = opts.staleMs ?? 3e4;
14604
15006
  const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
14605
15007
  const pollIntervalMs = opts.pollIntervalMs ?? 100;
@@ -14667,9 +15069,9 @@ var init_fileLock = __esm({
14667
15069
  });
14668
15070
 
14669
15071
  // src/lib/installLock.ts
14670
- import { resolve as resolve9 } from "path";
15072
+ import { resolve as resolve11 } from "path";
14671
15073
  function withInstallLock(projectRoot, fn, opts = {}) {
14672
- const lockPath = resolve9(projectRoot, ".massu", "installAll.lock");
15074
+ const lockPath = resolve11(projectRoot, ".massu", "installAll.lock");
14673
15075
  return withFileLockSync(
14674
15076
  lockPath,
14675
15077
  fn,
@@ -14705,8 +15107,8 @@ __export(config_refresh_exports, {
14705
15107
  mergeRefresh: () => mergeRefresh,
14706
15108
  runConfigRefresh: () => runConfigRefresh
14707
15109
  });
14708
- import { existsSync as existsSync13, readFileSync as readFileSync14, rmSync as rmSync4 } from "fs";
14709
- import { resolve as resolve10 } from "path";
15110
+ import { existsSync as existsSync14, readFileSync as readFileSync15, rmSync as rmSync4 } from "fs";
15111
+ import { resolve as resolve12 } from "path";
14710
15112
  import { parse as parseYaml5 } from "yaml";
14711
15113
  function flatten(obj, prefix3 = "") {
14712
15114
  const out = {};
@@ -14834,17 +15236,17 @@ function renderDiff(diff) {
14834
15236
  }
14835
15237
  async function runConfigRefresh(opts = {}) {
14836
15238
  const cwd = opts.cwd ?? process.cwd();
14837
- const configPath = resolve10(cwd, "massu.config.yaml");
15239
+ const configPath = resolve12(cwd, "massu.config.yaml");
14838
15240
  const log = opts.silent ? () => {
14839
15241
  } : (s) => process.stdout.write(s);
14840
- if (!existsSync13(configPath)) {
15242
+ if (!existsSync14(configPath)) {
14841
15243
  const message = "massu.config.yaml not found. Run: npx massu init";
14842
15244
  if (!opts.silent) process.stderr.write(message + "\n");
14843
15245
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
14844
15246
  }
14845
15247
  let existing;
14846
15248
  try {
14847
- const content = readFileSync14(configPath, "utf-8");
15249
+ const content = readFileSync15(configPath, "utf-8");
14848
15250
  const parsed = parseYaml5(content);
14849
15251
  if (!parsed || typeof parsed !== "object") {
14850
15252
  throw new Error("config is not a YAML object");
@@ -14917,8 +15319,8 @@ async function runConfigRefresh(opts = {}) {
14917
15319
  `);
14918
15320
  const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
14919
15321
  if (stackResolved) {
14920
- const placeholderPath = resolve10(installResult.claudeDir, "commands", "_massu-needs-stack.md");
14921
- if (existsSync13(placeholderPath)) {
15322
+ const placeholderPath = resolve12(installResult.claudeDir, "commands", "_massu-needs-stack.md");
15323
+ if (existsSync14(placeholderPath)) {
14922
15324
  try {
14923
15325
  rmSync4(placeholderPath, { force: true });
14924
15326
  log("Removed _massu-needs-stack.md (stack now declared).\n");
@@ -15027,12 +15429,12 @@ var init_gitToplevel = __esm({
15027
15429
  });
15028
15430
 
15029
15431
  // src/watch/lockfile-detector.ts
15030
- import { existsSync as existsSync14, statSync as statSync8 } from "fs";
15031
- import { resolve as resolve11 } from "path";
15432
+ import { existsSync as existsSync15, statSync as statSync8 } from "fs";
15433
+ import { resolve as resolve13 } from "path";
15032
15434
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
15033
15435
  for (const lf of KNOWN_LOCKFILES) {
15034
- const p19 = resolve11(projectRoot, lf);
15035
- if (!existsSync14(p19)) continue;
15436
+ const p19 = resolve13(projectRoot, lf);
15437
+ if (!existsSync15(p19)) continue;
15036
15438
  try {
15037
15439
  const stat = statSync8(p19);
15038
15440
  const delta = now - stat.mtimeMs;
@@ -15045,7 +15447,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
15045
15447
  function gitMidOperation(projectRoot) {
15046
15448
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
15047
15449
  for (const s of sentinels) {
15048
- if (existsSync14(resolve11(projectRoot, ".git", s))) return true;
15450
+ if (existsSync15(resolve13(projectRoot, ".git", s))) return true;
15049
15451
  }
15050
15452
  return false;
15051
15453
  }
@@ -15242,18 +15644,18 @@ var init_paths = __esm({
15242
15644
  });
15243
15645
 
15244
15646
  // src/watch/state.ts
15245
- import { closeSync as closeSync3, existsSync as existsSync15, fsyncSync as fsyncSync3, mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync15, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync4, writeSync as writeSync3 } from "fs";
15246
- import { dirname as dirname8, resolve as resolve12 } from "path";
15647
+ import { closeSync as closeSync3, existsSync as existsSync16, fsyncSync as fsyncSync3, mkdirSync as mkdirSync7, openSync as openSync3, readFileSync as readFileSync16, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync4, writeSync as writeSync3 } from "fs";
15648
+ import { dirname as dirname9, resolve as resolve14 } from "path";
15247
15649
  function watchStatePath(projectRoot) {
15248
- return resolve12(projectRoot, ".massu", "watch-state.json");
15650
+ return resolve14(projectRoot, ".massu", "watch-state.json");
15249
15651
  }
15250
15652
  function backupStatePath(projectRoot) {
15251
- return resolve12(projectRoot, ".massu", "watch-state.v0.bak.json");
15653
+ return resolve14(projectRoot, ".massu", "watch-state.v0.bak.json");
15252
15654
  }
15253
15655
  function readState(projectRoot) {
15254
15656
  const path = watchStatePath(projectRoot);
15255
- if (!existsSync15(path)) return { ...DEFAULT_STATE };
15256
- const content = readFileSync15(path, "utf-8");
15657
+ if (!existsSync16(path)) return { ...DEFAULT_STATE };
15658
+ const content = readFileSync16(path, "utf-8");
15257
15659
  let raw;
15258
15660
  try {
15259
15661
  raw = JSON.parse(content);
@@ -15295,14 +15697,14 @@ function readState(projectRoot) {
15295
15697
  }
15296
15698
  function archiveCorrupt(projectRoot, content) {
15297
15699
  const bak = backupStatePath(projectRoot);
15298
- mkdirSync6(dirname8(bak), { recursive: true });
15700
+ mkdirSync7(dirname9(bak), { recursive: true });
15299
15701
  writeFileSync4(bak, content, "utf-8");
15300
15702
  }
15301
15703
  function writeStateAtomic(projectRoot, state) {
15302
15704
  const path = watchStatePath(projectRoot);
15303
15705
  writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
15304
15706
  const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
15305
- mkdirSync6(dirname8(path), { recursive: true });
15707
+ mkdirSync7(dirname9(path), { recursive: true });
15306
15708
  let renamed = false;
15307
15709
  try {
15308
15710
  const fd = openSync3(tmp, "w");
@@ -15316,7 +15718,7 @@ function writeStateAtomic(projectRoot, state) {
15316
15718
  renameSync4(tmp, path);
15317
15719
  renamed = true;
15318
15720
  } finally {
15319
- if (!renamed && existsSync15(tmp)) {
15721
+ if (!renamed && existsSync16(tmp)) {
15320
15722
  try {
15321
15723
  rmSync5(tmp, { force: true });
15322
15724
  } catch {
@@ -15596,8 +15998,8 @@ __export(watch_exports, {
15596
15998
  runWatch: () => runWatch
15597
15999
  });
15598
16000
  import { spawnSync as spawnSync3 } from "child_process";
15599
- import { basename as basename5, dirname as dirname9, resolve as resolve13 } from "path";
15600
- import { appendFileSync, existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync16 } from "fs";
16001
+ import { basename as basename5, dirname as dirname10, resolve as resolve15 } from "path";
16002
+ import { appendFileSync, existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync17 } from "fs";
15601
16003
  function parseFlags(args2) {
15602
16004
  const out = {
15603
16005
  foreground: false,
@@ -15622,8 +16024,8 @@ function parseFlags(args2) {
15622
16024
  }
15623
16025
  function findClaudeBg() {
15624
16026
  const home = process.env.HOME ?? "";
15625
- const fixed = home ? resolve13(home, ".claude", "bin", "claude-bg") : null;
15626
- if (fixed && existsSync16(fixed)) return fixed;
16027
+ const fixed = home ? resolve15(home, ".claude", "bin", "claude-bg") : null;
16028
+ if (fixed && existsSync17(fixed)) return fixed;
15627
16029
  const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
15628
16030
  if (which.status === 0 && which.stdout) {
15629
16031
  const p19 = which.stdout.trim();
@@ -15684,7 +16086,7 @@ async function runWatch(args2) {
15684
16086
  }
15685
16087
  function runStatus(root) {
15686
16088
  const path = watchStatePath(root);
15687
- if (!existsSync16(path)) {
16089
+ if (!existsSync17(path)) {
15688
16090
  process.stdout.write("massu watch: not running (no state file)\n");
15689
16091
  return { exitCode: 0 };
15690
16092
  }
@@ -15766,7 +16168,7 @@ async function runForeground(root) {
15766
16168
  }
15767
16169
  throw err;
15768
16170
  }
15769
- return new Promise((resolve36) => {
16171
+ return new Promise((resolve38) => {
15770
16172
  const shutdown = async () => {
15771
16173
  if (stopped) return;
15772
16174
  stopped = true;
@@ -15776,7 +16178,7 @@ async function runForeground(root) {
15776
16178
  process.chdir(priorCwd);
15777
16179
  } catch {
15778
16180
  }
15779
- resolve36({ exitCode: 0 });
16181
+ resolve38({ exitCode: 0 });
15780
16182
  };
15781
16183
  process.on("SIGINT", () => {
15782
16184
  void shutdown();
@@ -15837,23 +16239,23 @@ async function runOnQuiescent(projectRoot) {
15837
16239
  );
15838
16240
  }
15839
16241
  function refreshLogPath(projectRoot) {
15840
- return resolve13(projectRoot, ".massu", "refresh-log.jsonl");
16242
+ return resolve15(projectRoot, ".massu", "refresh-log.jsonl");
15841
16243
  }
15842
16244
  function appendRefreshLog(projectRoot, event) {
15843
16245
  const path = refreshLogPath(projectRoot);
15844
16246
  try {
15845
- mkdirSync7(dirname9(path), { recursive: true });
16247
+ mkdirSync8(dirname10(path), { recursive: true });
15846
16248
  appendFileSync(path, JSON.stringify(event) + "\n", "utf-8");
15847
16249
  } catch {
15848
16250
  }
15849
16251
  }
15850
16252
  function readRefreshLog(projectRoot, limit = 10, opts = {}) {
15851
16253
  const path = refreshLogPath(projectRoot);
15852
- if (!existsSync16(path)) return [];
16254
+ if (!existsSync17(path)) return [];
15853
16255
  const warn = opts.warn ?? ((s) => {
15854
16256
  process.stderr.write(s);
15855
16257
  });
15856
- const lines = readFileSync16(path, "utf-8").split("\n").filter(Boolean);
16258
+ const lines = readFileSync17(path, "utf-8").split("\n").filter(Boolean);
15857
16259
  const tail = lines.slice(-limit);
15858
16260
  const out = [];
15859
16261
  let corrupt = 0;
@@ -15926,26 +16328,26 @@ var init_refresh_log = __esm({
15926
16328
  import {
15927
16329
  chmodSync as chmodSync3,
15928
16330
  closeSync as closeSync4,
15929
- existsSync as existsSync17,
16331
+ existsSync as existsSync18,
15930
16332
  fsyncSync as fsyncSync4,
15931
- mkdirSync as mkdirSync8,
16333
+ mkdirSync as mkdirSync9,
15932
16334
  openSync as openSync4,
15933
16335
  renameSync as renameSync5,
15934
16336
  rmSync as rmSync6,
15935
16337
  statSync as statSync9,
15936
16338
  writeSync as writeSync4
15937
16339
  } from "node:fs";
15938
- import { dirname as dirname10 } from "node:path";
16340
+ import { dirname as dirname11 } from "node:path";
15939
16341
  function atomicWrite(path, content, opts = {}) {
15940
16342
  const tmpPath = `${path}.tmp`;
15941
- const parentDir = dirname10(path);
16343
+ const parentDir = dirname11(path);
15942
16344
  try {
15943
- if (!existsSync17(parentDir)) {
16345
+ if (!existsSync18(parentDir)) {
15944
16346
  const mkdirOpts = { recursive: true };
15945
16347
  if (opts.ensureParentDirMode !== void 0) {
15946
16348
  mkdirOpts.mode = opts.ensureParentDirMode;
15947
16349
  }
15948
- mkdirSync8(parentDir, mkdirOpts);
16350
+ mkdirSync9(parentDir, mkdirOpts);
15949
16351
  }
15950
16352
  const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
15951
16353
  const openMode = opts.mode ?? 420;
@@ -15962,7 +16364,7 @@ function atomicWrite(path, content, opts = {}) {
15962
16364
  renameSync5(tmpPath, path);
15963
16365
  return { written: true };
15964
16366
  } catch (err) {
15965
- if (existsSync17(tmpPath)) {
16367
+ if (existsSync18(tmpPath)) {
15966
16368
  try {
15967
16369
  rmSync6(tmpPath, { force: true });
15968
16370
  } catch {
@@ -16042,10 +16444,10 @@ var init_registry_pubkey_generated = __esm({
16042
16444
  });
16043
16445
 
16044
16446
  // src/security/adapter-verifier.ts
16045
- import { createHash as createHash5 } from "node:crypto";
16447
+ import { createHash as createHash6 } from "node:crypto";
16046
16448
  import nacl from "tweetnacl";
16047
- function sha256Hex(bytes) {
16048
- return createHash5("sha256").update(bytes).digest("hex");
16449
+ function sha256Hex2(bytes) {
16450
+ return createHash6("sha256").update(bytes).digest("hex");
16049
16451
  }
16050
16452
  function reviver(key, value) {
16051
16453
  if (key === "__proto__" || key === "constructor" || key === "prototype") {
@@ -16088,7 +16490,7 @@ function verifyManifest(input) {
16088
16490
  if (manifestBytes.length === 0) {
16089
16491
  return { ok: false, reason: "manifest_b64 decoded to zero bytes" };
16090
16492
  }
16091
- const computedSha = sha256Hex(manifestBytes);
16493
+ const computedSha = sha256Hex2(manifestBytes);
16092
16494
  if (computedSha !== envelope.manifest_sha256) {
16093
16495
  return {
16094
16496
  ok: false,
@@ -16142,10 +16544,10 @@ function verifyManifest(input) {
16142
16544
  if (!sigOk) {
16143
16545
  return {
16144
16546
  ok: false,
16145
- reason: `Ed25519 signature verification failed against bundled public key (fingerprint ${sha256Hex(publicKey).slice(0, 16)}...). Manifest was either signed by a different key OR the signature is corrupt.`
16547
+ reason: `Ed25519 signature verification failed against bundled public key (fingerprint ${sha256Hex2(publicKey).slice(0, 16)}...). Manifest was either signed by a different key OR the signature is corrupt.`
16146
16548
  };
16147
16549
  }
16148
- const expectedKeyId = sha256Hex(publicKey);
16550
+ const expectedKeyId = sha256Hex2(publicKey);
16149
16551
  if (envelope.signing_key_id !== expectedKeyId) {
16150
16552
  return {
16151
16553
  ok: false,
@@ -16305,24 +16707,24 @@ var init_fetcher = __esm({
16305
16707
  });
16306
16708
 
16307
16709
  // src/security/manifest-cache.ts
16308
- import { existsSync as existsSync18, readFileSync as readFileSync17, statSync as statSync10 } from "node:fs";
16309
- import { homedir as homedir4 } from "node:os";
16310
- import { resolve as resolve14 } from "node:path";
16710
+ import { existsSync as existsSync19, readFileSync as readFileSync18, statSync as statSync10 } from "node:fs";
16711
+ import { homedir as homedir5 } from "node:os";
16712
+ import { resolve as resolve16 } from "node:path";
16311
16713
  import { z as z4 } from "zod";
16312
16714
  function defaultCachePaths() {
16313
- const dir = resolve14(homedir4(), ".massu");
16715
+ const dir = resolve16(homedir5(), ".massu");
16314
16716
  return {
16315
- cachePath: resolve14(dir, "adapter-manifest.json"),
16316
- lockPath: resolve14(dir, ".adapter-manifest.lock")
16717
+ cachePath: resolve16(dir, "adapter-manifest.json"),
16718
+ lockPath: resolve16(dir, ".adapter-manifest.lock")
16317
16719
  };
16318
16720
  }
16319
16721
  function loadCachedManifest(paths = defaultCachePaths()) {
16320
- if (!existsSync18(paths.cachePath)) {
16722
+ if (!existsSync19(paths.cachePath)) {
16321
16723
  return { kind: "absent" };
16322
16724
  }
16323
16725
  let raw;
16324
16726
  try {
16325
- const content = readFileSync17(paths.cachePath, "utf-8");
16727
+ const content = readFileSync18(paths.cachePath, "utf-8");
16326
16728
  raw = JSON.parse(content);
16327
16729
  } catch (err) {
16328
16730
  return {
@@ -16511,15 +16913,15 @@ var init_adapter_origin = __esm({
16511
16913
  });
16512
16914
 
16513
16915
  // src/security/local-fingerprint.ts
16514
- import { existsSync as existsSync19, readFileSync as readFileSync18, lstatSync as lstatSync5 } from "node:fs";
16515
- import { homedir as homedir5 } from "node:os";
16516
- import { resolve as resolve15, isAbsolute } from "node:path";
16517
- import { createHash as createHash6 } from "node:crypto";
16916
+ import { existsSync as existsSync20, readFileSync as readFileSync19, lstatSync as lstatSync5 } from "node:fs";
16917
+ import { homedir as homedir6 } from "node:os";
16918
+ import { resolve as resolve17, isAbsolute } from "node:path";
16919
+ import { createHash as createHash7 } from "node:crypto";
16518
16920
  import { z as z5 } from "zod";
16519
16921
  function computeLocalFingerprint(localPaths, projectRoot) {
16520
16922
  const tuples = [];
16521
16923
  for (const p19 of localPaths) {
16522
- const abs = isAbsolute(p19) ? p19 : resolve15(projectRoot, p19);
16924
+ const abs = isAbsolute(p19) ? p19 : resolve17(projectRoot, p19);
16523
16925
  let contentTag;
16524
16926
  try {
16525
16927
  const lst = lstatSync5(abs);
@@ -16528,7 +16930,7 @@ function computeLocalFingerprint(localPaths, projectRoot) {
16528
16930
  } else if (!lst.isFile()) {
16529
16931
  contentTag = "<not-a-file>";
16530
16932
  } else {
16531
- contentTag = createHash6("sha256").update(readFileSync18(abs)).digest("hex");
16933
+ contentTag = createHash7("sha256").update(readFileSync19(abs)).digest("hex");
16532
16934
  }
16533
16935
  } catch {
16534
16936
  contentTag = "<missing>";
@@ -16537,13 +16939,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
16537
16939
  }
16538
16940
  tuples.sort((a2, b2) => a2.path < b2.path ? -1 : a2.path > b2.path ? 1 : 0);
16539
16941
  const canonical = JSON.stringify(tuples);
16540
- return createHash6("sha256").update(canonical).digest("hex");
16942
+ return createHash7("sha256").update(canonical).digest("hex");
16541
16943
  }
16542
16944
  function readFingerprintSentinel(path = FINGERPRINT_PATH) {
16543
- if (!existsSync19(path)) return null;
16945
+ if (!existsSync20(path)) return null;
16544
16946
  let raw;
16545
16947
  try {
16546
- raw = JSON.parse(readFileSync18(path, "utf-8"));
16948
+ raw = JSON.parse(readFileSync19(path, "utf-8"));
16547
16949
  } catch {
16548
16950
  return null;
16549
16951
  }
@@ -16591,7 +16993,7 @@ var init_local_fingerprint = __esm({
16591
16993
  "use strict";
16592
16994
  init_atomic_write();
16593
16995
  init_manifest_schema();
16594
- FINGERPRINT_PATH = resolve15(homedir5(), ".massu", "adapters-local-fingerprint.json");
16996
+ FINGERPRINT_PATH = resolve17(homedir6(), ".massu", "adapters-local-fingerprint.json");
16595
16997
  FingerprintSentinelSchema = z5.object({
16596
16998
  fingerprint: z5.string().regex(/^[0-9a-f]{64}$/),
16597
16999
  source: z5.enum(["cli", "cli-resync"]),
@@ -16607,15 +17009,15 @@ var init_local_fingerprint = __esm({
16607
17009
  });
16608
17010
 
16609
17011
  // src/security/install-tracking.ts
16610
- import { readFileSync as readFileSync19, readdirSync as readdirSync12, lstatSync as lstatSync6, existsSync as existsSync20 } from "node:fs";
16611
- import { join as join10, relative as relative5, sep } from "node:path";
16612
- import { homedir as homedir6 } from "node:os";
16613
- import { resolve as resolve16 } from "node:path";
16614
- import { createHash as createHash7 } from "node:crypto";
17012
+ import { readFileSync as readFileSync20, readdirSync as readdirSync12, lstatSync as lstatSync6, existsSync as existsSync21 } from "node:fs";
17013
+ import { join as join11, relative as relative5, sep } from "node:path";
17014
+ import { homedir as homedir7 } from "node:os";
17015
+ import { resolve as resolve18 } from "node:path";
17016
+ import { createHash as createHash8 } from "node:crypto";
16615
17017
  import { z as z6 } from "zod";
16616
17018
  function containsHiddenDirs(packageDir) {
16617
17019
  for (const hidden of EXCLUDED_DIR_NAMES) {
16618
- if (existsSync20(`${packageDir}/${hidden}`)) {
17020
+ if (existsSync21(`${packageDir}/${hidden}`)) {
16619
17021
  return hidden;
16620
17022
  }
16621
17023
  }
@@ -16632,7 +17034,7 @@ function sha256OfDir(dir, opts = {}) {
16632
17034
  return;
16633
17035
  }
16634
17036
  for (const entry of entries.sort()) {
16635
- const absPath = join10(currentDir, entry);
17037
+ const absPath = join11(currentDir, entry);
16636
17038
  let lst;
16637
17039
  try {
16638
17040
  lst = lstatSync6(absPath);
@@ -16659,9 +17061,9 @@ function sha256OfDir(dir, opts = {}) {
16659
17061
  }
16660
17062
  walk(dir);
16661
17063
  files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
16662
- const top = createHash7("sha256");
17064
+ const top = createHash8("sha256");
16663
17065
  for (const f2 of files) {
16664
- const fileHash = createHash7("sha256").update(readFileSync19(f2.absPath)).digest("hex");
17066
+ const fileHash = createHash8("sha256").update(readFileSync20(f2.absPath)).digest("hex");
16665
17067
  top.update(f2.relativePath, "utf-8");
16666
17068
  top.update("\0", "utf-8");
16667
17069
  top.update(fileHash, "utf-8");
@@ -16670,10 +17072,10 @@ function sha256OfDir(dir, opts = {}) {
16670
17072
  return top.digest("hex");
16671
17073
  }
16672
17074
  function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
16673
- if (!existsSync20(path)) return {};
17075
+ if (!existsSync21(path)) return {};
16674
17076
  let raw;
16675
17077
  try {
16676
- raw = JSON.parse(readFileSync19(path, "utf-8"));
17078
+ raw = JSON.parse(readFileSync20(path, "utf-8"));
16677
17079
  } catch {
16678
17080
  return {};
16679
17081
  }
@@ -16737,7 +17139,7 @@ var init_install_tracking = __esm({
16737
17139
  "src/security/install-tracking.ts"() {
16738
17140
  "use strict";
16739
17141
  init_atomic_write();
16740
- INSTALLED_MANIFEST_PATH = resolve16(homedir6(), ".massu", "adapter-manifest-installed.json");
17142
+ INSTALLED_MANIFEST_PATH = resolve18(homedir7(), ".massu", "adapter-manifest-installed.json");
16741
17143
  DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
16742
17144
  EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
16743
17145
  InstallEntrySchema = z6.object({
@@ -16760,12 +17162,12 @@ var init_install_tracking = __esm({
16760
17162
  });
16761
17163
 
16762
17164
  // src/detect/adapters/discover.ts
16763
- import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync20, lstatSync as lstatSync7 } from "node:fs";
16764
- import { resolve as resolve17, isAbsolute as isAbsolute2 } from "node:path";
17165
+ import { existsSync as existsSync22, readdirSync as readdirSync13, readFileSync as readFileSync21, lstatSync as lstatSync7 } from "node:fs";
17166
+ import { resolve as resolve19, isAbsolute as isAbsolute2 } from "node:path";
16765
17167
  import { z as z7 } from "zod";
16766
17168
  function walkNodeModules(projectRoot, warnings) {
16767
- const nodeModulesDir = resolve17(projectRoot, "node_modules");
16768
- if (!existsSync21(nodeModulesDir)) {
17169
+ const nodeModulesDir = resolve19(projectRoot, "node_modules");
17170
+ if (!existsSync22(nodeModulesDir)) {
16769
17171
  return [];
16770
17172
  }
16771
17173
  const candidates = [];
@@ -16778,7 +17180,7 @@ function walkNodeModules(projectRoot, warnings) {
16778
17180
  }
16779
17181
  for (const entry of topLevelEntries) {
16780
17182
  if (entry.startsWith(".")) continue;
16781
- const entryPath = resolve17(nodeModulesDir, entry);
17183
+ const entryPath = resolve19(nodeModulesDir, entry);
16782
17184
  let entryStat;
16783
17185
  try {
16784
17186
  entryStat = lstatSync7(entryPath);
@@ -16800,7 +17202,7 @@ function walkNodeModules(projectRoot, warnings) {
16800
17202
  continue;
16801
17203
  }
16802
17204
  for (const sub of scopedEntries) {
16803
- const subPath = resolve17(entryPath, sub);
17205
+ const subPath = resolve19(entryPath, sub);
16804
17206
  let subStat;
16805
17207
  try {
16806
17208
  subStat = lstatSync7(subPath);
@@ -16819,11 +17221,11 @@ function walkNodeModules(projectRoot, warnings) {
16819
17221
  return candidates;
16820
17222
  }
16821
17223
  function tryReadAdapterPackage(packageDir, warnings) {
16822
- const pkgJsonPath = resolve17(packageDir, "package.json");
16823
- if (!existsSync21(pkgJsonPath)) return null;
17224
+ const pkgJsonPath = resolve19(packageDir, "package.json");
17225
+ if (!existsSync22(pkgJsonPath)) return null;
16824
17226
  let raw;
16825
17227
  try {
16826
- raw = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
17228
+ raw = JSON.parse(readFileSync21(pkgJsonPath, "utf-8"));
16827
17229
  } catch (err) {
16828
17230
  warnings.push(
16829
17231
  `skipping ${packageDir}: package.json parse failed (${err instanceof Error ? err.message : String(err)})`
@@ -16957,8 +17359,8 @@ function discoverAdapters(opts) {
16957
17359
  const localSet = new Set(opts.configLocalPaths);
16958
17360
  for (const localPath of opts.configLocalPaths) {
16959
17361
  if (seenIds.has(localPath)) continue;
16960
- const absPath = isAbsolute2(localPath) ? localPath : resolve17(opts.projectRoot, localPath);
16961
- if (!existsSync21(absPath)) {
17362
+ const absPath = isAbsolute2(localPath) ? localPath : resolve19(opts.projectRoot, localPath);
17363
+ if (!existsSync22(absPath)) {
16962
17364
  warnings.push(
16963
17365
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
16964
17366
  );
@@ -17039,8 +17441,8 @@ __export(adapters_exports, {
17039
17441
  runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
17040
17442
  runAdaptersSearch: () => runAdaptersSearch
17041
17443
  });
17042
- import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
17043
- import { resolve as resolve18 } from "node:path";
17444
+ import { existsSync as existsSync23, readFileSync as readFileSync22 } from "node:fs";
17445
+ import { resolve as resolve20 } from "node:path";
17044
17446
  import { parseDocument } from "yaml";
17045
17447
  async function handleAdaptersSubcommand(args2) {
17046
17448
  const sub = args2[0];
@@ -17307,8 +17709,8 @@ function mutateLocalArray(mutator, command) {
17307
17709
  );
17308
17710
  return { exitCode: 2 };
17309
17711
  }
17310
- const yamlPath = resolve18(projectRoot, "massu.config.yaml");
17311
- if (!existsSync22(yamlPath)) {
17712
+ const yamlPath = resolve20(projectRoot, "massu.config.yaml");
17713
+ if (!existsSync23(yamlPath)) {
17312
17714
  process.stderr.write(
17313
17715
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
17314
17716
  `
@@ -17317,7 +17719,7 @@ function mutateLocalArray(mutator, command) {
17317
17719
  }
17318
17720
  let yamlText;
17319
17721
  try {
17320
- yamlText = readFileSync21(yamlPath, "utf-8");
17722
+ yamlText = readFileSync22(yamlPath, "utf-8");
17321
17723
  } catch (err) {
17322
17724
  process.stderr.write(`${command}: failed to read ${yamlPath}: ${err instanceof Error ? err.message : String(err)}
17323
17725
  `);
@@ -17338,7 +17740,7 @@ function mutateLocalArray(mutator, command) {
17338
17740
  if (next === null) {
17339
17741
  return { exitCode: 0 };
17340
17742
  }
17341
- const lockPath = resolve18(projectRoot, ".massu", "adapters-local-mutate.lock");
17743
+ const lockPath = resolve20(projectRoot, ".massu", "adapters-local-mutate.lock");
17342
17744
  return withFileLockSync(lockPath, () => {
17343
17745
  doc.setIn(["adapters", "local"], next);
17344
17746
  const newYaml = doc.toString();
@@ -17407,23 +17809,23 @@ async function runAdaptersInstall(args2) {
17407
17809
  `);
17408
17810
  return { exitCode: 1 };
17409
17811
  }
17410
- const packageDir = resolve18(projectRoot, "node_modules", ...packageName.split("/"));
17411
- if (!existsSync22(packageDir)) {
17812
+ const packageDir = resolve20(projectRoot, "node_modules", ...packageName.split("/"));
17813
+ if (!existsSync23(packageDir)) {
17412
17814
  process.stderr.write(
17413
17815
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
17414
17816
  `
17415
17817
  );
17416
17818
  return { exitCode: 1 };
17417
17819
  }
17418
- const pkgJsonPath = resolve18(packageDir, "package.json");
17419
- if (!existsSync22(pkgJsonPath)) {
17820
+ const pkgJsonPath = resolve20(packageDir, "package.json");
17821
+ if (!existsSync23(pkgJsonPath)) {
17420
17822
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
17421
17823
  `);
17422
17824
  return { exitCode: 1 };
17423
17825
  }
17424
17826
  let pkgJson;
17425
17827
  try {
17426
- pkgJson = JSON.parse(readFileSync21(pkgJsonPath, "utf-8"));
17828
+ pkgJson = JSON.parse(readFileSync22(pkgJsonPath, "utf-8"));
17427
17829
  } catch (err) {
17428
17830
  process.stderr.write(`install: ${packageName} has malformed package.json: ${err instanceof Error ? err.message : String(err)}
17429
17831
  `);
@@ -17542,8 +17944,8 @@ async function runAdaptersResign(_args) {
17542
17944
  removeInstalledManifestEntry(name);
17543
17945
  continue;
17544
17946
  }
17545
- const packageDir = resolve18(projectRoot, "node_modules", ...name.split("/"));
17546
- if (!existsSync22(packageDir)) {
17947
+ const packageDir = resolve20(projectRoot, "node_modules", ...name.split("/"));
17948
+ if (!existsSync23(packageDir)) {
17547
17949
  removed++;
17548
17950
  warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
17549
17951
  removeInstalledManifestEntry(name);
@@ -17630,11 +18032,11 @@ var init_adapters2 = __esm({
17630
18032
 
17631
18033
  // src/db.ts
17632
18034
  import Database2 from "better-sqlite3";
17633
- import { dirname as dirname11, join as join11 } from "path";
17634
- import { existsSync as existsSync23, mkdirSync as mkdirSync9, readdirSync as readdirSync14, statSync as statSync11 } from "fs";
18035
+ import { dirname as dirname12, join as join12 } from "path";
18036
+ import { existsSync as existsSync24, mkdirSync as mkdirSync10, readdirSync as readdirSync14, statSync as statSync11 } from "fs";
17635
18037
  function getCodeGraphDb() {
17636
18038
  const dbPath = getResolvedPaths().codegraphDbPath;
17637
- if (!existsSync23(dbPath)) {
18039
+ if (!existsSync24(dbPath)) {
17638
18040
  throw new CodegraphDbNotInitializedError(dbPath);
17639
18041
  }
17640
18042
  const db = new Database2(dbPath, { readonly: true });
@@ -17643,9 +18045,9 @@ function getCodeGraphDb() {
17643
18045
  }
17644
18046
  function getDataDb() {
17645
18047
  const dbPath = getResolvedPaths().dataDbPath;
17646
- const dir = dirname11(dbPath);
17647
- if (!existsSync23(dir)) {
17648
- mkdirSync9(dir, { recursive: true });
18048
+ const dir = dirname12(dbPath);
18049
+ if (!existsSync24(dir)) {
18050
+ mkdirSync10(dir, { recursive: true });
17649
18051
  }
17650
18052
  const db = new Database2(dbPath);
17651
18053
  db.pragma("journal_mode = WAL");
@@ -17917,7 +18319,7 @@ function isPythonDataStale(dataDb, pythonRoot) {
17917
18319
  try {
17918
18320
  const entries = readdirSync14(dir, { withFileTypes: true });
17919
18321
  for (const entry of entries) {
17920
- const fullPath = join11(dir, entry.name);
18322
+ const fullPath = join12(dir, entry.name);
17921
18323
  if (entry.isDirectory()) {
17922
18324
  if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
17923
18325
  if (checkDir(fullPath)) return true;
@@ -17951,10 +18353,10 @@ var init_db = __esm({
17951
18353
  });
17952
18354
 
17953
18355
  // src/security-utils.ts
17954
- import { resolve as resolve19, normalize } from "path";
18356
+ import { resolve as resolve21, normalize } from "path";
17955
18357
  function ensureWithinRoot(filePath, projectRoot) {
17956
- const resolvedRoot = resolve19(projectRoot);
17957
- const resolvedPath = resolve19(resolvedRoot, filePath);
18358
+ const resolvedRoot = resolve21(projectRoot);
18359
+ const resolvedPath = resolve21(resolvedRoot, filePath);
17958
18360
  const normalizedPath = normalize(resolvedPath);
17959
18361
  const normalizedRoot = normalize(resolvedRoot);
17960
18362
  if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
@@ -18027,8 +18429,8 @@ var init_rules = __esm({
18027
18429
  });
18028
18430
 
18029
18431
  // src/import-resolver.ts
18030
- import { readFileSync as readFileSync22, existsSync as existsSync24, statSync as statSync12 } from "fs";
18031
- import { resolve as resolve20, dirname as dirname12, join as join12 } from "path";
18432
+ import { readFileSync as readFileSync23, existsSync as existsSync25, statSync as statSync12 } from "fs";
18433
+ import { resolve as resolve22, dirname as dirname13, join as join13 } from "path";
18032
18434
  function parseImports(source) {
18033
18435
  const imports = [];
18034
18436
  const lines = source.split("\n");
@@ -18084,23 +18486,23 @@ function resolveImportPath(specifier, fromFile) {
18084
18486
  let basePath;
18085
18487
  if (specifier.startsWith("@/")) {
18086
18488
  const paths = getResolvedPaths();
18087
- basePath = resolve20(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
18489
+ basePath = resolve22(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
18088
18490
  } else {
18089
- basePath = resolve20(dirname12(fromFile), specifier);
18491
+ basePath = resolve22(dirname13(fromFile), specifier);
18090
18492
  }
18091
- if (existsSync24(basePath) && !isDirectory(basePath)) {
18493
+ if (existsSync25(basePath) && !isDirectory(basePath)) {
18092
18494
  return toRelative(basePath);
18093
18495
  }
18094
18496
  const resolvedPaths = getResolvedPaths();
18095
18497
  for (const ext of resolvedPaths.extensions) {
18096
18498
  const withExt = basePath + ext;
18097
- if (existsSync24(withExt)) {
18499
+ if (existsSync25(withExt)) {
18098
18500
  return toRelative(withExt);
18099
18501
  }
18100
18502
  }
18101
18503
  for (const indexFile of resolvedPaths.indexFiles) {
18102
- const indexPath = join12(basePath, indexFile);
18103
- if (existsSync24(indexPath)) {
18504
+ const indexPath = join13(basePath, indexFile);
18505
+ if (existsSync25(indexPath)) {
18104
18506
  return toRelative(indexPath);
18105
18507
  }
18106
18508
  }
@@ -18136,11 +18538,11 @@ function buildImportIndex(dataDb, codegraphDb) {
18136
18538
  const batchSize = 500;
18137
18539
  let batch = [];
18138
18540
  for (const file of files) {
18139
- const absPath = ensureWithinRoot(resolve20(projectRoot, file.path), projectRoot);
18140
- if (!existsSync24(absPath)) continue;
18541
+ const absPath = ensureWithinRoot(resolve22(projectRoot, file.path), projectRoot);
18542
+ if (!existsSync25(absPath)) continue;
18141
18543
  let source;
18142
18544
  try {
18143
- source = readFileSync22(absPath, "utf-8");
18545
+ source = readFileSync23(absPath, "utf-8");
18144
18546
  } catch {
18145
18547
  continue;
18146
18548
  }
@@ -18176,15 +18578,15 @@ var init_import_resolver = __esm({
18176
18578
  });
18177
18579
 
18178
18580
  // src/trpc-index.ts
18179
- import { readFileSync as readFileSync23, existsSync as existsSync25, readdirSync as readdirSync15 } from "fs";
18180
- import { resolve as resolve21, join as join13 } from "path";
18581
+ import { readFileSync as readFileSync24, existsSync as existsSync26, readdirSync as readdirSync15 } from "fs";
18582
+ import { resolve as resolve23, join as join14 } from "path";
18181
18583
  function parseRootRouter() {
18182
18584
  const paths = getResolvedPaths();
18183
18585
  const rootPath = paths.rootRouterPath;
18184
- if (!existsSync25(rootPath)) {
18586
+ if (!existsSync26(rootPath)) {
18185
18587
  throw new Error(`Root router not found at ${rootPath}`);
18186
18588
  }
18187
- const source = readFileSync23(rootPath, "utf-8");
18589
+ const source = readFileSync24(rootPath, "utf-8");
18188
18590
  const mappings = [];
18189
18591
  const importMap = /* @__PURE__ */ new Map();
18190
18592
  const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
@@ -18192,16 +18594,16 @@ function parseRootRouter() {
18192
18594
  while ((match = importRegex.exec(source)) !== null) {
18193
18595
  const variable = match[1];
18194
18596
  let filePath = match[2];
18195
- const fullPath = resolve21(paths.routersDir, filePath);
18597
+ const fullPath = resolve23(paths.routersDir, filePath);
18196
18598
  for (const ext of [".ts", ".tsx", ""]) {
18197
18599
  const candidate = fullPath + ext;
18198
18600
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
18199
- if (existsSync25(candidate)) {
18601
+ if (existsSync26(candidate)) {
18200
18602
  filePath = routersRelPath + "/" + filePath + ext;
18201
18603
  break;
18202
18604
  }
18203
- const indexCandidate = join13(fullPath, "index.ts");
18204
- if (existsSync25(indexCandidate)) {
18605
+ const indexCandidate = join14(fullPath, "index.ts");
18606
+ if (existsSync26(indexCandidate)) {
18205
18607
  filePath = routersRelPath + "/" + filePath + "/index.ts";
18206
18608
  break;
18207
18609
  }
@@ -18220,9 +18622,9 @@ function parseRootRouter() {
18220
18622
  return mappings;
18221
18623
  }
18222
18624
  function extractProcedures(routerFilePath) {
18223
- const absPath = resolve21(getProjectRoot(), routerFilePath);
18224
- if (!existsSync25(absPath)) return [];
18225
- const source = readFileSync23(absPath, "utf-8");
18625
+ const absPath = resolve23(getProjectRoot(), routerFilePath);
18626
+ if (!existsSync26(absPath)) return [];
18627
+ const source = readFileSync24(absPath, "utf-8");
18226
18628
  const procedures = [];
18227
18629
  const seen = /* @__PURE__ */ new Set();
18228
18630
  const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
@@ -18245,13 +18647,13 @@ function findUICallSites(routerKey, procedureName) {
18245
18647
  const root = getProjectRoot();
18246
18648
  const src = config.paths.source;
18247
18649
  const searchDirs = [
18248
- resolve21(root, config.paths.pages ?? src + "/app"),
18249
- resolve21(root, config.paths.components ?? src + "/components"),
18250
- resolve21(root, config.paths.hooks ?? src + "/hooks")
18650
+ resolve23(root, config.paths.pages ?? src + "/app"),
18651
+ resolve23(root, config.paths.components ?? src + "/components"),
18652
+ resolve23(root, config.paths.hooks ?? src + "/hooks")
18251
18653
  ];
18252
18654
  const searchPattern = `api.${routerKey}.${procedureName}`;
18253
18655
  for (const dir of searchDirs) {
18254
- if (!existsSync25(dir)) continue;
18656
+ if (!existsSync26(dir)) continue;
18255
18657
  searchDirectory(dir, searchPattern, callSites);
18256
18658
  }
18257
18659
  return callSites;
@@ -18259,13 +18661,13 @@ function findUICallSites(routerKey, procedureName) {
18259
18661
  function searchDirectory(dir, pattern, results) {
18260
18662
  const entries = readdirSync15(dir, { withFileTypes: true });
18261
18663
  for (const entry of entries) {
18262
- const fullPath = join13(dir, entry.name);
18664
+ const fullPath = join14(dir, entry.name);
18263
18665
  if (entry.isDirectory()) {
18264
18666
  if (entry.name === "node_modules" || entry.name === ".next") continue;
18265
18667
  searchDirectory(fullPath, pattern, results);
18266
18668
  } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
18267
18669
  try {
18268
- const source = readFileSync23(fullPath, "utf-8");
18670
+ const source = readFileSync24(fullPath, "utf-8");
18269
18671
  const lines = source.split("\n");
18270
18672
  for (let i = 0; i < lines.length; i++) {
18271
18673
  if (lines[i].includes(pattern)) {
@@ -18328,8 +18730,8 @@ var init_trpc_index = __esm({
18328
18730
  });
18329
18731
 
18330
18732
  // src/page-deps.ts
18331
- import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
18332
- import { resolve as resolve22 } from "path";
18733
+ import { readFileSync as readFileSync25, existsSync as existsSync27 } from "fs";
18734
+ import { resolve as resolve24 } from "path";
18333
18735
  function deriveRoute(pageFile) {
18334
18736
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
18335
18737
  return route || "/";
@@ -18367,10 +18769,10 @@ function findRouterCalls(files) {
18367
18769
  const routers = /* @__PURE__ */ new Set();
18368
18770
  const projectRoot = getProjectRoot();
18369
18771
  for (const file of files) {
18370
- const absPath = ensureWithinRoot(resolve22(projectRoot, file), projectRoot);
18371
- if (!existsSync26(absPath)) continue;
18772
+ const absPath = ensureWithinRoot(resolve24(projectRoot, file), projectRoot);
18773
+ if (!existsSync27(absPath)) continue;
18372
18774
  try {
18373
- const source = readFileSync24(absPath, "utf-8");
18775
+ const source = readFileSync25(absPath, "utf-8");
18374
18776
  const apiCallRegex = /api\.(\w+)\.\w+/g;
18375
18777
  let match;
18376
18778
  while ((match = apiCallRegex.exec(source)) !== null) {
@@ -18388,10 +18790,10 @@ function findTablesFromRouters(routerNames, dataDb) {
18388
18790
  "SELECT DISTINCT router_file FROM massu_trpc_procedures WHERE router_name = ?"
18389
18791
  ).all(routerName);
18390
18792
  for (const proc of procs) {
18391
- const absPath = ensureWithinRoot(resolve22(getProjectRoot(), proc.router_file), getProjectRoot());
18392
- if (!existsSync26(absPath)) continue;
18793
+ const absPath = ensureWithinRoot(resolve24(getProjectRoot(), proc.router_file), getProjectRoot());
18794
+ if (!existsSync27(absPath)) continue;
18393
18795
  try {
18394
- const source = readFileSync24(absPath, "utf-8");
18796
+ const source = readFileSync25(absPath, "utf-8");
18395
18797
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
18396
18798
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
18397
18799
  const tableRegex = new RegExp(regexStr + "\\.", "g");
@@ -18660,14 +19062,14 @@ var init_domains = __esm({
18660
19062
  });
18661
19063
 
18662
19064
  // src/schema-mapper.ts
18663
- import { readFileSync as readFileSync25, existsSync as existsSync27, readdirSync as readdirSync16 } from "fs";
18664
- import { join as join14 } from "path";
19065
+ import { readFileSync as readFileSync26, existsSync as existsSync28, readdirSync as readdirSync16 } from "fs";
19066
+ import { join as join15 } from "path";
18665
19067
  function parsePrismaSchema() {
18666
19068
  const schemaPath = getResolvedPaths().prismaSchemaPath;
18667
- if (!existsSync27(schemaPath)) {
19069
+ if (!existsSync28(schemaPath)) {
18668
19070
  throw new Error(`Prisma schema not found at ${schemaPath}`);
18669
19071
  }
18670
- const source = readFileSync25(schemaPath, "utf-8");
19072
+ const source = readFileSync26(schemaPath, "utf-8");
18671
19073
  const models = [];
18672
19074
  const sourceLines = source.split("\n");
18673
19075
  let i = 0;
@@ -18725,14 +19127,14 @@ function toSnakeCase(str) {
18725
19127
  function findColumnUsageInRouters(tableName) {
18726
19128
  const usage = /* @__PURE__ */ new Map();
18727
19129
  const routersDir = getResolvedPaths().routersDir;
18728
- if (!existsSync27(routersDir)) return usage;
19130
+ if (!existsSync28(routersDir)) return usage;
18729
19131
  scanDirectory(routersDir, tableName, usage);
18730
19132
  return usage;
18731
19133
  }
18732
19134
  function scanDirectory(dir, tableName, usage) {
18733
19135
  const entries = readdirSync16(dir, { withFileTypes: true });
18734
19136
  for (const entry of entries) {
18735
- const fullPath = join14(dir, entry.name);
19137
+ const fullPath = join15(dir, entry.name);
18736
19138
  if (entry.isDirectory()) {
18737
19139
  scanDirectory(fullPath, tableName, usage);
18738
19140
  } else if (entry.name.endsWith(".ts")) {
@@ -18742,7 +19144,7 @@ function scanDirectory(dir, tableName, usage) {
18742
19144
  }
18743
19145
  function scanFile(absPath, tableName, usage) {
18744
19146
  try {
18745
- const source = readFileSync25(absPath, "utf-8");
19147
+ const source = readFileSync26(absPath, "utf-8");
18746
19148
  if (!source.includes(tableName)) return;
18747
19149
  const relPath = absPath.slice(getProjectRoot().length + 1);
18748
19150
  const lines = source.split("\n");
@@ -18787,15 +19189,15 @@ function detectMismatches(models) {
18787
19189
  }
18788
19190
  function findFilesUsingColumn(dir, column, tableName) {
18789
19191
  const result = [];
18790
- if (!existsSync27(dir)) return result;
19192
+ if (!existsSync28(dir)) return result;
18791
19193
  const entries = readdirSync16(dir, { withFileTypes: true });
18792
19194
  for (const entry of entries) {
18793
- const fullPath = join14(dir, entry.name);
19195
+ const fullPath = join15(dir, entry.name);
18794
19196
  if (entry.isDirectory()) {
18795
19197
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
18796
19198
  } else if (entry.name.endsWith(".ts")) {
18797
19199
  try {
18798
- const source = readFileSync25(fullPath, "utf-8");
19200
+ const source = readFileSync26(fullPath, "utf-8");
18799
19201
  if (source.includes(tableName) && source.includes(column)) {
18800
19202
  result.push(fullPath.slice(getProjectRoot().length + 1));
18801
19203
  }
@@ -18940,34 +19342,34 @@ var init_import_parser = __esm({
18940
19342
  });
18941
19343
 
18942
19344
  // src/python/import-resolver.ts
18943
- import { readFileSync as readFileSync26, existsSync as existsSync28, readdirSync as readdirSync17 } from "fs";
18944
- import { resolve as resolve24, join as join15, relative as relative6, dirname as dirname13 } from "path";
19345
+ import { readFileSync as readFileSync27, existsSync as existsSync29, readdirSync as readdirSync17 } from "fs";
19346
+ import { resolve as resolve26, join as join16, relative as relative6, dirname as dirname14 } from "path";
18945
19347
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
18946
19348
  const projectRoot = getProjectRoot();
18947
19349
  if (level > 0) {
18948
- let baseDir = dirname13(resolve24(projectRoot, fromFile));
19350
+ let baseDir = dirname14(resolve26(projectRoot, fromFile));
18949
19351
  for (let i = 1; i < level; i++) {
18950
- baseDir = dirname13(baseDir);
19352
+ baseDir = dirname14(baseDir);
18951
19353
  }
18952
19354
  const modulePart = module.replace(/^\.+/, "");
18953
19355
  if (modulePart) {
18954
19356
  const parts2 = modulePart.split(".");
18955
- return tryResolvePythonPath(join15(baseDir, ...parts2), projectRoot);
19357
+ return tryResolvePythonPath(join16(baseDir, ...parts2), projectRoot);
18956
19358
  }
18957
19359
  return tryResolvePythonPath(baseDir, projectRoot);
18958
19360
  }
18959
19361
  const parts = module.split(".");
18960
- const candidate = join15(resolve24(projectRoot, pythonRoot), ...parts);
19362
+ const candidate = join16(resolve26(projectRoot, pythonRoot), ...parts);
18961
19363
  return tryResolvePythonPath(candidate, projectRoot);
18962
19364
  }
18963
19365
  function tryResolvePythonPath(basePath, projectRoot) {
18964
- if (existsSync28(basePath + ".py")) {
19366
+ if (existsSync29(basePath + ".py")) {
18965
19367
  return relative6(projectRoot, basePath + ".py");
18966
19368
  }
18967
- if (existsSync28(join15(basePath, "__init__.py"))) {
18968
- return relative6(projectRoot, join15(basePath, "__init__.py"));
19369
+ if (existsSync29(join16(basePath, "__init__.py"))) {
19370
+ return relative6(projectRoot, join16(basePath, "__init__.py"));
18969
19371
  }
18970
- if (basePath.endsWith(".py") && existsSync28(basePath)) {
19372
+ if (basePath.endsWith(".py") && existsSync29(basePath)) {
18971
19373
  return relative6(projectRoot, basePath);
18972
19374
  }
18973
19375
  return null;
@@ -18979,9 +19381,9 @@ function walkPythonFiles(dir, excludeDirs) {
18979
19381
  for (const entry of entries) {
18980
19382
  if (entry.isDirectory()) {
18981
19383
  if (excludeDirs.includes(entry.name)) continue;
18982
- files.push(...walkPythonFiles(join15(dir, entry.name), excludeDirs));
19384
+ files.push(...walkPythonFiles(join16(dir, entry.name), excludeDirs));
18983
19385
  } else if (entry.name.endsWith(".py")) {
18984
- files.push(join15(dir, entry.name));
19386
+ files.push(join16(dir, entry.name));
18985
19387
  }
18986
19388
  }
18987
19389
  } catch {
@@ -18990,7 +19392,7 @@ function walkPythonFiles(dir, excludeDirs) {
18990
19392
  }
18991
19393
  function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
18992
19394
  const projectRoot = getProjectRoot();
18993
- const absRoot = resolve24(projectRoot, pythonRoot);
19395
+ const absRoot = resolve26(projectRoot, pythonRoot);
18994
19396
  dataDb.exec("DELETE FROM massu_py_imports");
18995
19397
  const insertStmt = dataDb.prepare(
18996
19398
  "INSERT INTO massu_py_imports (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)"
@@ -19007,7 +19409,7 @@ function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__"
19007
19409
  const relFile = relative6(projectRoot, absFile);
19008
19410
  let source;
19009
19411
  try {
19010
- source = readFileSync26(absFile, "utf-8");
19412
+ source = readFileSync27(absFile, "utf-8");
19011
19413
  } catch {
19012
19414
  continue;
19013
19415
  }
@@ -19273,8 +19675,8 @@ var init_route_parser = __esm({
19273
19675
  });
19274
19676
 
19275
19677
  // src/python/route-indexer.ts
19276
- import { readFileSync as readFileSync27, readdirSync as readdirSync18 } from "fs";
19277
- import { join as join16, relative as relative7 } from "path";
19678
+ import { readFileSync as readFileSync28, readdirSync as readdirSync18 } from "fs";
19679
+ import { join as join17, relative as relative7 } from "path";
19278
19680
  function walkPyFiles(dir, excludeDirs) {
19279
19681
  const files = [];
19280
19682
  try {
@@ -19282,9 +19684,9 @@ function walkPyFiles(dir, excludeDirs) {
19282
19684
  for (const entry of entries) {
19283
19685
  if (entry.isDirectory()) {
19284
19686
  if (excludeDirs.includes(entry.name)) continue;
19285
- files.push(...walkPyFiles(join16(dir, entry.name), excludeDirs));
19687
+ files.push(...walkPyFiles(join17(dir, entry.name), excludeDirs));
19286
19688
  } else if (entry.name.endsWith(".py")) {
19287
- files.push(join16(dir, entry.name));
19689
+ files.push(join17(dir, entry.name));
19288
19690
  }
19289
19691
  }
19290
19692
  } catch {
@@ -19293,7 +19695,7 @@ function walkPyFiles(dir, excludeDirs) {
19293
19695
  }
19294
19696
  function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
19295
19697
  const projectRoot = getProjectRoot();
19296
- const absRoot = join16(projectRoot, pythonRoot);
19698
+ const absRoot = join17(projectRoot, pythonRoot);
19297
19699
  dataDb.exec("DELETE FROM massu_py_routes");
19298
19700
  dataDb.exec("DELETE FROM massu_py_route_callers");
19299
19701
  const insertStmt = dataDb.prepare(
@@ -19306,7 +19708,7 @@ function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
19306
19708
  const relFile = relative7(projectRoot, absFile);
19307
19709
  let source;
19308
19710
  try {
19309
- source = readFileSync27(absFile, "utf-8");
19711
+ source = readFileSync28(absFile, "utf-8");
19310
19712
  } catch {
19311
19713
  continue;
19312
19714
  }
@@ -19517,8 +19919,8 @@ var init_model_parser = __esm({
19517
19919
  });
19518
19920
 
19519
19921
  // src/python/model-indexer.ts
19520
- import { readFileSync as readFileSync28, readdirSync as readdirSync19 } from "fs";
19521
- import { join as join17, relative as relative8 } from "path";
19922
+ import { readFileSync as readFileSync29, readdirSync as readdirSync19 } from "fs";
19923
+ import { join as join18, relative as relative8 } from "path";
19522
19924
  function walkPyFiles2(dir, excludeDirs) {
19523
19925
  const files = [];
19524
19926
  try {
@@ -19526,9 +19928,9 @@ function walkPyFiles2(dir, excludeDirs) {
19526
19928
  for (const entry of entries) {
19527
19929
  if (entry.isDirectory()) {
19528
19930
  if (excludeDirs.includes(entry.name)) continue;
19529
- files.push(...walkPyFiles2(join17(dir, entry.name), excludeDirs));
19931
+ files.push(...walkPyFiles2(join18(dir, entry.name), excludeDirs));
19530
19932
  } else if (entry.name.endsWith(".py")) {
19531
- files.push(join17(dir, entry.name));
19933
+ files.push(join18(dir, entry.name));
19532
19934
  }
19533
19935
  }
19534
19936
  } catch {
@@ -19537,7 +19939,7 @@ function walkPyFiles2(dir, excludeDirs) {
19537
19939
  }
19538
19940
  function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
19539
19941
  const projectRoot = getProjectRoot();
19540
- const absRoot = join17(projectRoot, pythonRoot);
19942
+ const absRoot = join18(projectRoot, pythonRoot);
19541
19943
  dataDb.exec("DELETE FROM massu_py_models");
19542
19944
  dataDb.exec("DELETE FROM massu_py_fk_edges");
19543
19945
  const insertModel = dataDb.prepare(
@@ -19553,7 +19955,7 @@ function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
19553
19955
  const relFile = relative8(projectRoot, absFile);
19554
19956
  let source;
19555
19957
  try {
19556
- source = readFileSync28(absFile, "utf-8");
19958
+ source = readFileSync29(absFile, "utf-8");
19557
19959
  } catch {
19558
19960
  continue;
19559
19961
  }
@@ -19813,19 +20215,19 @@ var init_migration_parser = __esm({
19813
20215
  });
19814
20216
 
19815
20217
  // src/python/migration-indexer.ts
19816
- import { readFileSync as readFileSync29, readdirSync as readdirSync20 } from "fs";
19817
- import { join as join18, relative as relative9 } from "path";
20218
+ import { readFileSync as readFileSync30, readdirSync as readdirSync20 } from "fs";
20219
+ import { join as join19, relative as relative9 } from "path";
19818
20220
  function buildPythonMigrationIndex(dataDb, alembicDir) {
19819
20221
  const projectRoot = getProjectRoot();
19820
- const absDir = join18(projectRoot, alembicDir);
20222
+ const absDir = join19(projectRoot, alembicDir);
19821
20223
  dataDb.exec("DELETE FROM massu_py_migrations");
19822
- const versionsDir = join18(absDir, "versions");
20224
+ const versionsDir = join19(absDir, "versions");
19823
20225
  let files = [];
19824
20226
  try {
19825
- files = readdirSync20(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join18(versionsDir, f2));
20227
+ files = readdirSync20(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join19(versionsDir, f2));
19826
20228
  } catch {
19827
20229
  try {
19828
- files = readdirSync20(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join18(absDir, f2));
20230
+ files = readdirSync20(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join19(absDir, f2));
19829
20231
  } catch {
19830
20232
  }
19831
20233
  }
@@ -19839,7 +20241,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
19839
20241
  for (const absFile of files) {
19840
20242
  let source;
19841
20243
  try {
19842
- source = readFileSync29(absFile, "utf-8");
20244
+ source = readFileSync30(absFile, "utf-8");
19843
20245
  } catch {
19844
20246
  continue;
19845
20247
  }
@@ -19873,12 +20275,12 @@ var init_migration_indexer = __esm({
19873
20275
  });
19874
20276
 
19875
20277
  // src/python/coupling-detector.ts
19876
- import { readFileSync as readFileSync30, readdirSync as readdirSync21 } from "fs";
19877
- import { join as join19, relative as relative10 } from "path";
20278
+ import { readFileSync as readFileSync31, readdirSync as readdirSync21 } from "fs";
20279
+ import { join as join20, relative as relative10 } from "path";
19878
20280
  function buildPythonCouplingIndex(dataDb) {
19879
20281
  const projectRoot = getProjectRoot();
19880
20282
  const config = getConfig();
19881
- const srcDir = join19(projectRoot, config.paths.source);
20283
+ const srcDir = join20(projectRoot, config.paths.source);
19882
20284
  const routes = dataDb.prepare("SELECT id, method, path FROM massu_py_routes").all();
19883
20285
  if (routes.length === 0) return 0;
19884
20286
  dataDb.exec("DELETE FROM massu_py_route_callers");
@@ -19910,7 +20312,7 @@ function buildPythonCouplingIndex(dataDb) {
19910
20312
  const relFile = relative10(projectRoot, absFile);
19911
20313
  let source;
19912
20314
  try {
19913
- source = readFileSync30(absFile, "utf-8");
20315
+ source = readFileSync31(absFile, "utf-8");
19914
20316
  } catch {
19915
20317
  continue;
19916
20318
  }
@@ -19942,9 +20344,9 @@ function walkFrontendFiles(dir) {
19942
20344
  for (const entry of entries) {
19943
20345
  if (entry.isDirectory()) {
19944
20346
  if (exclude.includes(entry.name)) continue;
19945
- files.push(...walkFrontendFiles(join19(dir, entry.name)));
20347
+ files.push(...walkFrontendFiles(join20(dir, entry.name)));
19946
20348
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
19947
- files.push(join19(dir, entry.name));
20349
+ files.push(join20(dir, entry.name));
19948
20350
  }
19949
20351
  }
19950
20352
  } catch {
@@ -20315,8 +20717,8 @@ var init_memory_tools = __esm({
20315
20717
  });
20316
20718
 
20317
20719
  // src/docs-tools.ts
20318
- import { readFileSync as readFileSync31, existsSync as existsSync29 } from "fs";
20319
- import { resolve as resolve25, basename as basename6 } from "path";
20720
+ import { readFileSync as readFileSync32, existsSync as existsSync30 } from "fs";
20721
+ import { resolve as resolve27, basename as basename6 } from "path";
20320
20722
  function p3(baseName) {
20321
20723
  return `${getConfig().toolPrefix}_${baseName}`;
20322
20724
  }
@@ -20371,10 +20773,10 @@ function handleDocsToolCall(name, args2) {
20371
20773
  }
20372
20774
  function loadDocsMap() {
20373
20775
  const mapPath = getResolvedPaths().docsMapPath;
20374
- if (!existsSync29(mapPath)) {
20776
+ if (!existsSync30(mapPath)) {
20375
20777
  throw new Error(`docs-map.json not found at ${mapPath}`);
20376
20778
  }
20377
- return JSON.parse(readFileSync31(mapPath, "utf-8"));
20779
+ return JSON.parse(readFileSync32(mapPath, "utf-8"));
20378
20780
  }
20379
20781
  function matchesPattern(filePath, pattern) {
20380
20782
  const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
@@ -20444,13 +20846,13 @@ function extractFrontmatter(content) {
20444
20846
  }
20445
20847
  function extractProcedureNames(routerPath) {
20446
20848
  const root = getProjectRoot();
20447
- const absPath = ensureWithinRoot(resolve25(getResolvedPaths().srcDir, "..", routerPath), root);
20448
- if (!existsSync29(absPath)) {
20449
- const altPath = ensureWithinRoot(resolve25(getResolvedPaths().srcDir, "../server/api/routers", basename6(routerPath)), root);
20450
- if (!existsSync29(altPath)) return [];
20451
- return extractProcedureNamesFromContent(readFileSync31(altPath, "utf-8"));
20849
+ const absPath = ensureWithinRoot(resolve27(getResolvedPaths().srcDir, "..", routerPath), root);
20850
+ if (!existsSync30(absPath)) {
20851
+ const altPath = ensureWithinRoot(resolve27(getResolvedPaths().srcDir, "../server/api/routers", basename6(routerPath)), root);
20852
+ if (!existsSync30(altPath)) return [];
20853
+ return extractProcedureNamesFromContent(readFileSync32(altPath, "utf-8"));
20452
20854
  }
20453
- return extractProcedureNamesFromContent(readFileSync31(absPath, "utf-8"));
20855
+ return extractProcedureNamesFromContent(readFileSync32(absPath, "utf-8"));
20454
20856
  }
20455
20857
  function extractProcedureNamesFromContent(content) {
20456
20858
  const procRegex = /\.(?:query|mutation)\s*\(/g;
@@ -20490,8 +20892,8 @@ function handleDocsAudit(args2) {
20490
20892
  for (const [mappingId, triggeringFiles] of affectedMappings) {
20491
20893
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
20492
20894
  if (!mapping) continue;
20493
- const helpPagePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
20494
- if (!existsSync29(helpPagePath)) {
20895
+ const helpPagePath = ensureWithinRoot(resolve27(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
20896
+ if (!existsSync30(helpPagePath)) {
20495
20897
  results.push({
20496
20898
  helpPage: mapping.helpPage,
20497
20899
  mappingId,
@@ -20503,7 +20905,7 @@ function handleDocsAudit(args2) {
20503
20905
  });
20504
20906
  continue;
20505
20907
  }
20506
- const content = readFileSync31(helpPagePath, "utf-8");
20908
+ const content = readFileSync32(helpPagePath, "utf-8");
20507
20909
  const sections = extractSections(content);
20508
20910
  const frontmatter = extractFrontmatter(content);
20509
20911
  const staleReasons = [];
@@ -20543,9 +20945,9 @@ function handleDocsAudit(args2) {
20543
20945
  });
20544
20946
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
20545
20947
  if (parentId === mappingId) {
20546
- const guidePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
20547
- if (existsSync29(guidePath)) {
20548
- const guideContent = readFileSync31(guidePath, "utf-8");
20948
+ const guidePath = ensureWithinRoot(resolve27(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
20949
+ if (existsSync30(guidePath)) {
20950
+ const guideContent = readFileSync32(guidePath, "utf-8");
20549
20951
  const guideFrontmatter = extractFrontmatter(guideContent);
20550
20952
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
20551
20953
  results.push({
@@ -20578,14 +20980,14 @@ function handleDocsCoverage(args2) {
20578
20980
  const gaps = [];
20579
20981
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
20580
20982
  for (const mapping of mappings) {
20581
- const helpPagePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
20582
- const exists = existsSync29(helpPagePath);
20983
+ const helpPagePath = ensureWithinRoot(resolve27(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
20984
+ const exists = existsSync30(helpPagePath);
20583
20985
  let hasContent = false;
20584
20986
  let lineCount = 0;
20585
20987
  let lastVerified = null;
20586
20988
  let status = null;
20587
20989
  if (exists) {
20588
- const content = readFileSync31(helpPagePath, "utf-8");
20990
+ const content = readFileSync32(helpPagePath, "utf-8");
20589
20991
  lineCount = content.split("\n").length;
20590
20992
  hasContent = lineCount > 10;
20591
20993
  const frontmatter = extractFrontmatter(content);
@@ -20926,8 +21328,8 @@ var init_observability_tools = __esm({
20926
21328
  });
20927
21329
 
20928
21330
  // src/sentinel-db.ts
20929
- import { existsSync as existsSync30 } from "fs";
20930
- import { resolve as resolve26 } from "path";
21331
+ import { existsSync as existsSync31 } from "fs";
21332
+ import { resolve as resolve28 } from "path";
20931
21333
  function parsePortalScope(raw) {
20932
21334
  if (!raw) return [];
20933
21335
  try {
@@ -21163,23 +21565,23 @@ function validateFeatures(db, domainFilter) {
21163
21565
  const missingProcedures = [];
21164
21566
  const missingPages = [];
21165
21567
  for (const comp of components) {
21166
- const absPath = resolve26(PROJECT_ROOT, comp.component_file);
21167
- if (!existsSync30(absPath)) {
21568
+ const absPath = resolve28(PROJECT_ROOT, comp.component_file);
21569
+ if (!existsSync31(absPath)) {
21168
21570
  missingComponents.push(comp.component_file);
21169
21571
  }
21170
21572
  }
21171
21573
  for (const proc of procedures) {
21172
- const routerPath = resolve26(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
21173
- if (!existsSync30(routerPath)) {
21574
+ const routerPath = resolve28(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
21575
+ if (!existsSync31(routerPath)) {
21174
21576
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
21175
21577
  }
21176
21578
  }
21177
21579
  for (const page of pages) {
21178
21580
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
21179
- const absPath = resolve26(PROJECT_ROOT, routeToPath);
21180
- if (page.page_route.startsWith("/") && !existsSync30(absPath)) {
21181
- const altPath = resolve26(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
21182
- if (!existsSync30(altPath)) {
21581
+ const absPath = resolve28(PROJECT_ROOT, routeToPath);
21582
+ if (page.page_route.startsWith("/") && !existsSync31(absPath)) {
21583
+ const altPath = resolve28(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
21584
+ if (!existsSync31(altPath)) {
21183
21585
  missingPages.push(page.page_route);
21184
21586
  }
21185
21587
  }
@@ -21703,8 +22105,8 @@ var init_sentinel_tools = __esm({
21703
22105
  });
21704
22106
 
21705
22107
  // src/sentinel-scanner.ts
21706
- import { readFileSync as readFileSync32, existsSync as existsSync31, readdirSync as readdirSync22, statSync as statSync13 } from "fs";
21707
- import { resolve as resolve27, join as join20, basename as basename7, dirname as dirname14, relative as relative11 } from "path";
22108
+ import { readFileSync as readFileSync33, existsSync as existsSync32, readdirSync as readdirSync22, statSync as statSync13 } from "fs";
22109
+ import { resolve as resolve29, join as join21, basename as basename7, dirname as dirname15, relative as relative11 } from "path";
21708
22110
  function inferDomain(filePath) {
21709
22111
  const domains = getConfig().domains;
21710
22112
  const path = filePath.toLowerCase();
@@ -21833,8 +22235,8 @@ function scanComponentExports(dataDb) {
21833
22235
  const projectRoot = getProjectRoot();
21834
22236
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
21835
22237
  const componentDirs = [];
21836
- const basePath = resolve27(projectRoot, componentsBase);
21837
- if (existsSync31(basePath)) {
22238
+ const basePath = resolve29(projectRoot, componentsBase);
22239
+ if (existsSync32(basePath)) {
21838
22240
  try {
21839
22241
  const entries = readdirSync22(basePath, { withFileTypes: true });
21840
22242
  for (const entry of entries) {
@@ -21846,12 +22248,12 @@ function scanComponentExports(dataDb) {
21846
22248
  }
21847
22249
  }
21848
22250
  for (const dir of componentDirs) {
21849
- const absDir = resolve27(projectRoot, dir);
21850
- if (!existsSync31(absDir)) continue;
22251
+ const absDir = resolve29(projectRoot, dir);
22252
+ if (!existsSync32(absDir)) continue;
21851
22253
  const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
21852
22254
  for (const file of files) {
21853
22255
  const relPath = relative11(projectRoot, file);
21854
- const source = readFileSync32(file, "utf-8");
22256
+ const source = readFileSync33(file, "utf-8");
21855
22257
  const annotations = parseFeatureAnnotations(source);
21856
22258
  if (annotations.length > 0) {
21857
22259
  for (const ann of annotations) {
@@ -21877,7 +22279,7 @@ function scanComponentExports(dataDb) {
21877
22279
  if (hasHandlers && exportMatch) {
21878
22280
  const componentName = exportMatch[1];
21879
22281
  const domain = inferDomain(relPath);
21880
- const subdomain = basename7(dirname14(relPath));
22282
+ const subdomain = basename7(dirname15(relPath));
21881
22283
  const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
21882
22284
  if (!annotations.some((a2) => a2.featureKey === featureKey)) {
21883
22285
  features.push({
@@ -21902,7 +22304,7 @@ function walkDir2(dir) {
21902
22304
  try {
21903
22305
  const entries = readdirSync22(dir);
21904
22306
  for (const entry of entries) {
21905
- const fullPath = join20(dir, entry);
22307
+ const fullPath = join21(dir, entry);
21906
22308
  try {
21907
22309
  const stat = statSync13(fullPath);
21908
22310
  if (stat.isDirectory()) {
@@ -22890,7 +23292,7 @@ var init_audit_trail = __esm({
22890
23292
  });
22891
23293
 
22892
23294
  // src/validation-engine.ts
22893
- import { existsSync as existsSync32, readFileSync as readFileSync33 } from "fs";
23295
+ import { existsSync as existsSync33, readFileSync as readFileSync34 } from "fs";
22894
23296
  function p10(baseName) {
22895
23297
  return `${getConfig().toolPrefix}_${baseName}`;
22896
23298
  }
@@ -22921,7 +23323,7 @@ function validateFile(filePath, projectRoot) {
22921
23323
  });
22922
23324
  return checks;
22923
23325
  }
22924
- if (!existsSync32(absPath)) {
23326
+ if (!existsSync33(absPath)) {
22925
23327
  checks.push({
22926
23328
  name: "file_exists",
22927
23329
  severity: "error",
@@ -22930,7 +23332,7 @@ function validateFile(filePath, projectRoot) {
22930
23332
  });
22931
23333
  return checks;
22932
23334
  }
22933
- const source = readFileSync33(absPath, "utf-8");
23335
+ const source = readFileSync34(absPath, "utf-8");
22934
23336
  const lines = source.split("\n");
22935
23337
  if (activeChecks.rule_compliance !== false) {
22936
23338
  for (const ruleSet of config.rules) {
@@ -23375,7 +23777,7 @@ var init_adr_generator = __esm({
23375
23777
  });
23376
23778
 
23377
23779
  // src/security-scorer.ts
23378
- import { existsSync as existsSync33, readFileSync as readFileSync34 } from "fs";
23780
+ import { existsSync as existsSync34, readFileSync as readFileSync35 } from "fs";
23379
23781
  function p12(baseName) {
23380
23782
  return `${getConfig().toolPrefix}_${baseName}`;
23381
23783
  }
@@ -23399,12 +23801,12 @@ function scoreFileSecurity(filePath, projectRoot) {
23399
23801
  }]
23400
23802
  };
23401
23803
  }
23402
- if (!existsSync33(absPath)) {
23804
+ if (!existsSync34(absPath)) {
23403
23805
  return { riskScore: 0, findings: [] };
23404
23806
  }
23405
23807
  let source;
23406
23808
  try {
23407
- source = readFileSync34(absPath, "utf-8");
23809
+ source = readFileSync35(absPath, "utf-8");
23408
23810
  } catch {
23409
23811
  return { riskScore: 0, findings: [] };
23410
23812
  }
@@ -23693,8 +24095,8 @@ var init_security_scorer = __esm({
23693
24095
  });
23694
24096
 
23695
24097
  // src/dependency-scorer.ts
23696
- import { existsSync as existsSync34, readFileSync as readFileSync35 } from "fs";
23697
- import { resolve as resolve28 } from "path";
24098
+ import { existsSync as existsSync35, readFileSync as readFileSync36 } from "fs";
24099
+ import { resolve as resolve30 } from "path";
23698
24100
  function p13(baseName) {
23699
24101
  return `${getConfig().toolPrefix}_${baseName}`;
23700
24102
  }
@@ -23726,10 +24128,10 @@ function calculateDepRisk(factors) {
23726
24128
  return Math.min(100, risk);
23727
24129
  }
23728
24130
  function getInstalledPackages(projectRoot) {
23729
- const pkgPath = resolve28(projectRoot, "package.json");
23730
- if (!existsSync34(pkgPath)) return /* @__PURE__ */ new Map();
24131
+ const pkgPath = resolve30(projectRoot, "package.json");
24132
+ if (!existsSync35(pkgPath)) return /* @__PURE__ */ new Map();
23731
24133
  try {
23732
- const pkg = JSON.parse(readFileSync35(pkgPath, "utf-8"));
24134
+ const pkg = JSON.parse(readFileSync36(pkgPath, "utf-8"));
23733
24135
  const packages = /* @__PURE__ */ new Map();
23734
24136
  for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
23735
24137
  packages.set(name, version);
@@ -24331,9 +24733,9 @@ var init_regression_detector = __esm({
24331
24733
  });
24332
24734
 
24333
24735
  // src/knowledge-indexer.ts
24334
- import { createHash as createHash8 } from "crypto";
24335
- import { readFileSync as readFileSync36, readdirSync as readdirSync23, statSync as statSync14, existsSync as existsSync35 } from "fs";
24336
- import { resolve as resolve29, relative as relative12, basename as basename8, extname as extname2 } from "path";
24736
+ import { createHash as createHash9 } from "crypto";
24737
+ import { readFileSync as readFileSync37, readdirSync as readdirSync23, statSync as statSync14, existsSync as existsSync36 } from "fs";
24738
+ import { resolve as resolve31, relative as relative12, basename as basename8, extname as extname2 } from "path";
24337
24739
  function getKnowledgePaths() {
24338
24740
  const resolved = getResolvedPaths();
24339
24741
  const config = getConfig();
@@ -24361,7 +24763,7 @@ function discoverMarkdownFiles(baseDir) {
24361
24763
  try {
24362
24764
  const entries = readdirSync23(dir, { withFileTypes: true });
24363
24765
  for (const entry of entries) {
24364
- const fullPath = resolve29(dir, entry.name);
24766
+ const fullPath = resolve31(dir, entry.name);
24365
24767
  if (entry.isDirectory()) {
24366
24768
  if (entry.name === "archive" && dir.includes("session-state")) continue;
24367
24769
  if (entry.name === "archive" && dir.includes("status")) continue;
@@ -24419,7 +24821,7 @@ function categorizeFile(filePath) {
24419
24821
  return "root";
24420
24822
  }
24421
24823
  function hashContent2(content) {
24422
- return createHash8("sha256").update(content).digest("hex");
24824
+ return createHash9("sha256").update(content).digest("hex");
24423
24825
  }
24424
24826
  function parseCRTable(content) {
24425
24827
  const rules = [];
@@ -24639,11 +25041,11 @@ function indexAllKnowledge(db) {
24639
25041
  files.push(...memFiles);
24640
25042
  } catch {
24641
25043
  }
24642
- if (existsSync35(paths.plansDir)) {
25044
+ if (existsSync36(paths.plansDir)) {
24643
25045
  const planFiles = discoverMarkdownFiles(paths.plansDir);
24644
25046
  files.push(...planFiles);
24645
25047
  }
24646
- if (existsSync35(paths.docsDir)) {
25048
+ if (existsSync36(paths.docsDir)) {
24647
25049
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
24648
25050
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
24649
25051
  files.push(...docsFiles);
@@ -24686,8 +25088,8 @@ function indexAllKnowledge(db) {
24686
25088
  } catch {
24687
25089
  }
24688
25090
  for (const filePath of files) {
24689
- if (!existsSync35(filePath)) continue;
24690
- const content = readFileSync36(filePath, "utf-8");
25091
+ if (!existsSync36(filePath)) continue;
25092
+ const content = readFileSync37(filePath, "utf-8");
24691
25093
  const hash = hashContent2(content);
24692
25094
  const relPath = filePath.startsWith(paths.claudeDir) ? relative12(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative12(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative12(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative12(paths.memoryDir, filePath)}` : basename8(filePath);
24693
25095
  const category = categorizeFile(filePath);
@@ -24807,10 +25209,10 @@ function isKnowledgeStale(db) {
24807
25209
  files.push(...discoverMarkdownFiles(paths.memoryDir));
24808
25210
  } catch {
24809
25211
  }
24810
- if (existsSync35(paths.plansDir)) {
25212
+ if (existsSync36(paths.plansDir)) {
24811
25213
  files.push(...discoverMarkdownFiles(paths.plansDir));
24812
25214
  }
24813
- if (existsSync35(paths.docsDir)) {
25215
+ if (existsSync36(paths.docsDir)) {
24814
25216
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
24815
25217
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
24816
25218
  files.push(...docsFiles);
@@ -24839,8 +25241,8 @@ var init_knowledge_indexer = __esm({
24839
25241
  });
24840
25242
 
24841
25243
  // src/knowledge-tools.ts
24842
- import { readFileSync as readFileSync37, writeFileSync as writeFileSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync24 } from "fs";
24843
- import { resolve as resolve30, basename as basename9 } from "path";
25244
+ import { readFileSync as readFileSync38, writeFileSync as writeFileSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync24 } from "fs";
25245
+ import { resolve as resolve32, basename as basename9 } from "path";
24844
25246
  function p16(baseName) {
24845
25247
  return `${getConfig().toolPrefix}_${baseName}`;
24846
25248
  }
@@ -25577,7 +25979,7 @@ function handleCorrect(db, args2) {
25577
25979
  if (!wrong || !correction || !rule) {
25578
25980
  return text15("Error: wrong, correction, and rule are all required.");
25579
25981
  }
25580
- const correctionsPath = resolve30(getResolvedPaths().memoryDir, "corrections.md");
25982
+ const correctionsPath = resolve32(getResolvedPaths().memoryDir, "corrections.md");
25581
25983
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
25582
25984
  const title = rule.slice(0, 60);
25583
25985
  const entry = `
@@ -25590,7 +25992,7 @@ ${crRule ? `- **CR**: ${crRule}
25590
25992
  `;
25591
25993
  let existing = "";
25592
25994
  try {
25593
- existing = readFileSync37(correctionsPath, "utf-8");
25995
+ existing = readFileSync38(correctionsPath, "utf-8");
25594
25996
  } catch {
25595
25997
  }
25596
25998
  const archiveIdx = existing.indexOf("## Archived");
@@ -25928,13 +26330,13 @@ var init_knowledge_tools = __esm({
25928
26330
 
25929
26331
  // src/knowledge-db.ts
25930
26332
  import Database3 from "better-sqlite3";
25931
- import { dirname as dirname15 } from "path";
25932
- import { existsSync as existsSync37, mkdirSync as mkdirSync10 } from "fs";
26333
+ import { dirname as dirname16 } from "path";
26334
+ import { existsSync as existsSync38, mkdirSync as mkdirSync11 } from "fs";
25933
26335
  function getKnowledgeDb() {
25934
26336
  const dbPath = getResolvedPaths().knowledgeDbPath;
25935
- const dir = dirname15(dbPath);
25936
- if (!existsSync37(dir)) {
25937
- mkdirSync10(dir, { recursive: true });
26337
+ const dir = dirname16(dbPath);
26338
+ if (!existsSync38(dir)) {
26339
+ mkdirSync11(dir, { recursive: true });
25938
26340
  }
25939
26341
  const db = new Database3(dbPath);
25940
26342
  db.pragma("journal_mode = WAL");
@@ -26667,8 +27069,8 @@ var init_python_tools = __esm({
26667
27069
  });
26668
27070
 
26669
27071
  // src/tools.ts
26670
- import { readFileSync as readFileSync38, existsSync as existsSync38 } from "fs";
26671
- import { resolve as resolve31, basename as basename10 } from "path";
27072
+ import { readFileSync as readFileSync39, existsSync as existsSync39 } from "fs";
27073
+ import { resolve as resolve33, basename as basename10 } from "path";
26672
27074
  function prefix2() {
26673
27075
  return getConfig().toolPrefix;
26674
27076
  }
@@ -26703,7 +27105,7 @@ function ensureIndexes(dataDb, codegraphDb, force = false) {
26703
27105
  if (config.python?.root) {
26704
27106
  const pythonRoot = config.python.root;
26705
27107
  const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
26706
- if (force || isPythonDataStale(dataDb, resolve31(getProjectRoot(), pythonRoot))) {
27108
+ if (force || isPythonDataStale(dataDb, resolve33(getProjectRoot(), pythonRoot))) {
26707
27109
  const pyImports = buildPythonImportIndex(dataDb, pythonRoot, excludeDirs);
26708
27110
  results.push(`Python imports: ${pyImports}`);
26709
27111
  const pyRoutes = buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs);
@@ -27138,9 +27540,9 @@ function handleContext(file, dataDb, codegraphDb) {
27138
27540
  try {
27139
27541
  const resolvedPaths = getResolvedPaths();
27140
27542
  const root = getProjectRoot();
27141
- const absFilePath = ensureWithinRoot(resolve31(resolvedPaths.srcDir, "..", file), root);
27142
- if (existsSync38(absFilePath)) {
27143
- const fileContent = readFileSync38(absFilePath, "utf-8").slice(0, 3e3);
27543
+ const absFilePath = ensureWithinRoot(resolve33(resolvedPaths.srcDir, "..", file), root);
27544
+ if (existsSync39(absFilePath)) {
27545
+ const fileContent = readFileSync39(absFilePath, "utf-8").slice(0, 3e3);
27144
27546
  const keywords = [];
27145
27547
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
27146
27548
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -27564,11 +27966,11 @@ function handleSchema(args2) {
27564
27966
  lines.push("Checking all column references against Prisma schema...");
27565
27967
  lines.push("");
27566
27968
  const projectRoot = getProjectRoot();
27567
- const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
27568
- if (!existsSync38(absPath)) {
27969
+ const absPath = ensureWithinRoot(resolve33(projectRoot, file), projectRoot);
27970
+ if (!existsSync39(absPath)) {
27569
27971
  return text17(`File not found: ${file}`);
27570
27972
  }
27571
- const source = readFileSync38(absPath, "utf-8");
27973
+ const source = readFileSync39(absPath, "utf-8");
27572
27974
  const config = getConfig();
27573
27975
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
27574
27976
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -27937,8 +28339,8 @@ var init_server_dispatch = __esm({
27937
28339
 
27938
28340
  // src/server.ts
27939
28341
  var server_exports = {};
27940
- import { readFileSync as readFileSync39 } from "fs";
27941
- import { resolve as resolve32, dirname as dirname16 } from "path";
28342
+ import { readFileSync as readFileSync40 } from "fs";
28343
+ import { resolve as resolve34, dirname as dirname17 } from "path";
27942
28344
  import { fileURLToPath as fileURLToPath4 } from "url";
27943
28345
  function pruneMemoryOnStartup() {
27944
28346
  try {
@@ -27970,10 +28372,10 @@ var init_server = __esm({
27970
28372
  init_memory_db();
27971
28373
  init_license();
27972
28374
  init_server_dispatch();
27973
- __dirname4 = dirname16(fileURLToPath4(import.meta.url));
28375
+ __dirname4 = dirname17(fileURLToPath4(import.meta.url));
27974
28376
  PKG_VERSION = (() => {
27975
28377
  try {
27976
- const pkg = JSON.parse(readFileSync39(resolve32(__dirname4, "..", "package.json"), "utf-8"));
28378
+ const pkg = JSON.parse(readFileSync40(resolve34(__dirname4, "..", "package.json"), "utf-8"));
27977
28379
  return pkg.version ?? "0.0.0";
27978
28380
  } catch {
27979
28381
  return "0.0.0";
@@ -28259,19 +28661,19 @@ var config_upgrade_exports = {};
28259
28661
  __export(config_upgrade_exports, {
28260
28662
  runConfigUpgrade: () => runConfigUpgrade
28261
28663
  });
28262
- import { existsSync as existsSync39, readFileSync as readFileSync40, writeFileSync as writeFileSync6, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
28263
- import { resolve as resolve33 } from "path";
28664
+ import { existsSync as existsSync40, readFileSync as readFileSync41, writeFileSync as writeFileSync6, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
28665
+ import { resolve as resolve35 } from "path";
28264
28666
  import { parse as parseYaml6 } from "yaml";
28265
28667
  async function runConfigUpgrade(opts = {}) {
28266
28668
  const cwd = opts.cwd ?? process.cwd();
28267
- const configPath = resolve33(cwd, "massu.config.yaml");
28669
+ const configPath = resolve35(cwd, "massu.config.yaml");
28268
28670
  const bakPath = `${configPath}.bak`;
28269
28671
  const log = opts.silent ? () => {
28270
28672
  } : (s) => process.stdout.write(s);
28271
28673
  const err = opts.silent ? () => {
28272
28674
  } : (s) => process.stderr.write(s);
28273
28675
  if (opts.rollback) {
28274
- if (!existsSync39(bakPath)) {
28676
+ if (!existsSync40(bakPath)) {
28275
28677
  const message = `No backup found at ${bakPath}`;
28276
28678
  err(message + "\n");
28277
28679
  return { exitCode: 1, action: "none", message };
@@ -28287,14 +28689,14 @@ async function runConfigUpgrade(opts = {}) {
28287
28689
  return { exitCode: 2, action: "none", message };
28288
28690
  }
28289
28691
  }
28290
- if (!existsSync39(configPath)) {
28692
+ if (!existsSync40(configPath)) {
28291
28693
  const message = "massu.config.yaml not found. Run: npx massu init";
28292
28694
  err(message + "\n");
28293
28695
  return { exitCode: 1, action: "none", message };
28294
28696
  }
28295
28697
  let existing;
28296
28698
  try {
28297
- const content = readFileSync40(configPath, "utf-8");
28699
+ const content = readFileSync41(configPath, "utf-8");
28298
28700
  const parsed = parseYaml6(content);
28299
28701
  if (!parsed || typeof parsed !== "object") {
28300
28702
  throw new Error("config is not a YAML object");
@@ -28317,7 +28719,7 @@ async function runConfigUpgrade(opts = {}) {
28317
28719
  fingerprint: computeFingerprint(detection)
28318
28720
  };
28319
28721
  try {
28320
- const original = readFileSync40(configPath, "utf-8");
28722
+ const original = readFileSync41(configPath, "utf-8");
28321
28723
  writeFileSync6(bakPath, original, "utf-8");
28322
28724
  } catch (e2) {
28323
28725
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
@@ -28351,8 +28753,8 @@ var config_check_drift_exports = {};
28351
28753
  __export(config_check_drift_exports, {
28352
28754
  runConfigCheckDrift: () => runConfigCheckDrift
28353
28755
  });
28354
- import { existsSync as existsSync40, readFileSync as readFileSync41 } from "fs";
28355
- import { resolve as resolve34 } from "path";
28756
+ import { existsSync as existsSync41, readFileSync as readFileSync42 } from "fs";
28757
+ import { resolve as resolve36 } from "path";
28356
28758
  import { parse as parseYaml7 } from "yaml";
28357
28759
  function renderChanges(changes) {
28358
28760
  if (changes.length === 0) return "(none)\n";
@@ -28360,12 +28762,12 @@ function renderChanges(changes) {
28360
28762
  }
28361
28763
  async function runConfigCheckDrift(opts = {}) {
28362
28764
  const cwd = opts.cwd ?? process.cwd();
28363
- const configPath = resolve34(cwd, "massu.config.yaml");
28765
+ const configPath = resolve36(cwd, "massu.config.yaml");
28364
28766
  const log = opts.silent ? () => {
28365
28767
  } : (s) => process.stdout.write(s);
28366
28768
  const err = opts.silent ? () => {
28367
28769
  } : (s) => process.stderr.write(s);
28368
- if (!existsSync40(configPath)) {
28770
+ if (!existsSync41(configPath)) {
28369
28771
  const message = "massu.config.yaml not found. Run: npx massu init";
28370
28772
  err(message + "\n");
28371
28773
  return {
@@ -28379,7 +28781,7 @@ async function runConfigCheckDrift(opts = {}) {
28379
28781
  }
28380
28782
  let config;
28381
28783
  try {
28382
- const content = readFileSync41(configPath, "utf-8");
28784
+ const content = readFileSync42(configPath, "utf-8");
28383
28785
  const parsed = parseYaml7(content);
28384
28786
  if (!parsed || typeof parsed !== "object") {
28385
28787
  throw new Error("config is not a YAML object");
@@ -28446,11 +28848,11 @@ var init_config_check_drift = __esm({
28446
28848
  });
28447
28849
 
28448
28850
  // src/cli.ts
28449
- import { readFileSync as readFileSync42 } from "fs";
28450
- import { resolve as resolve35, dirname as dirname17 } from "path";
28851
+ import { readFileSync as readFileSync43 } from "fs";
28852
+ import { resolve as resolve37, dirname as dirname18 } from "path";
28451
28853
  import { fileURLToPath as fileURLToPath5 } from "url";
28452
28854
  var __filename4 = fileURLToPath5(import.meta.url);
28453
- var __dirname5 = dirname17(__filename4);
28855
+ var __dirname5 = dirname18(__filename4);
28454
28856
  var args = process.argv.slice(2);
28455
28857
  var subcommand = args[0];
28456
28858
  async function main() {
@@ -28475,6 +28877,12 @@ async function main() {
28475
28877
  await runInstallCommands2();
28476
28878
  break;
28477
28879
  }
28880
+ case "permissions": {
28881
+ const { handlePermissionsSubcommand: handlePermissionsSubcommand2 } = await Promise.resolve().then(() => (init_permissions2(), permissions_exports));
28882
+ const result = await handlePermissionsSubcommand2(args.slice(1));
28883
+ process.exit(result.exitCode);
28884
+ return;
28885
+ }
28478
28886
  case "show-template": {
28479
28887
  const { runShowTemplate: runShowTemplate2 } = await Promise.resolve().then(() => (init_show_template(), show_template_exports));
28480
28888
  await runShowTemplate2(args.slice(1));
@@ -28587,12 +28995,13 @@ Commands:
28587
28995
  init Set up Massu AI in your project (one command, full setup)
28588
28996
  doctor Check installation health
28589
28997
  install-hooks Install/update Claude Code hooks
28590
- install-commands Install/update slash commands
28998
+ install-commands Install/update slash commands (use --skip-permissions to opt out of MCP allowlist seeding)
28591
28999
  show-template Print the resolved variant of a bundled template (e.g. for diffs)
28592
29000
  watch Run the file-watcher daemon (auto-refresh on stack changes)
28593
29001
  refresh-log [N] Show the last N watcher auto-refresh events
28594
29002
  validate-config Validate massu.config.yaml (alias: config validate)
28595
29003
  config <sub> Config lifecycle: refresh | validate | upgrade | doctor | check-drift
29004
+ permissions <sub> MCP permission lifecycle: install | verify | check-drift
28596
29005
  adapters <sub> Third-party adapter registry: list | refresh | search | add-local | remove-local | install | resign
28597
29006
 
28598
29007
  Options:
@@ -28632,7 +29041,7 @@ Examples:
28632
29041
  }
28633
29042
  function printVersion() {
28634
29043
  try {
28635
- const pkg = JSON.parse(readFileSync42(resolve35(__dirname5, "../package.json"), "utf-8"));
29044
+ const pkg = JSON.parse(readFileSync43(resolve37(__dirname5, "../package.json"), "utf-8"));
28636
29045
  console.log(`massu v${pkg.version}`);
28637
29046
  } catch {
28638
29047
  console.log("massu v0.1.0");