@massu/core 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -0
- package/commands/massu-release.md +23 -1
- package/dist/cli.js +1252 -597
- package/package.json +1 -1
- package/src/changelog-generator.ts +178 -0
- package/src/cli.ts +15 -1
- package/src/commands/changelog.ts +165 -0
- package/src/commands/doctor.ts +4 -3
- package/src/commands/init.ts +3 -10
- package/src/commands/install-commands.ts +62 -53
- package/src/commands/permissions.ts +150 -0
- package/src/lib/settings-local.ts +110 -0
- package/src/permissions.ts +422 -0
- package/src/security/registry-pubkey.generated.ts +1 -1
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
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
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
|
|
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 =
|
|
1948
|
-
if (
|
|
2218
|
+
const nodeModulesPath = resolve4(cwd, "node_modules/@massu/core", assetName);
|
|
2219
|
+
if (existsSync5(nodeModulesPath)) {
|
|
1949
2220
|
return nodeModulesPath;
|
|
1950
2221
|
}
|
|
1951
|
-
const distRelPath =
|
|
1952
|
-
if (
|
|
2222
|
+
const distRelPath = resolve4(__dirname, "..", assetName);
|
|
2223
|
+
if (existsSync5(distRelPath)) {
|
|
1953
2224
|
return distRelPath;
|
|
1954
2225
|
}
|
|
1955
|
-
const srcRelPath =
|
|
1956
|
-
if (
|
|
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
|
|
2236
|
+
return createHash2("sha256").update(content, "utf-8").digest("hex");
|
|
1966
2237
|
}
|
|
1967
2238
|
function loadManifest(claudeDir) {
|
|
1968
|
-
const path =
|
|
1969
|
-
if (!
|
|
2239
|
+
const path = resolve4(claudeDir, MANIFEST_RELPATH);
|
|
2240
|
+
if (!existsSync5(path)) {
|
|
1970
2241
|
return emptyManifest();
|
|
1971
2242
|
}
|
|
1972
2243
|
try {
|
|
1973
|
-
const raw =
|
|
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 =
|
|
2007
|
-
if (!
|
|
2008
|
-
|
|
2255
|
+
const dir = resolve4(claudeDir, ".massu");
|
|
2256
|
+
if (!existsSync5(dir)) {
|
|
2257
|
+
mkdirSync3(dir, { recursive: true });
|
|
2009
2258
|
}
|
|
2010
|
-
const finalPath =
|
|
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 =
|
|
2073
|
-
if (
|
|
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 =
|
|
2078
|
-
if (
|
|
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 =
|
|
2083
|
-
if (
|
|
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 (!
|
|
2100
|
-
|
|
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 =
|
|
2353
|
+
const sourcePath = resolve4(sourceDir, entry);
|
|
2105
2354
|
const entryStat = statSync(sourcePath);
|
|
2106
2355
|
if (entryStat.isDirectory()) {
|
|
2107
|
-
const subTargetDir =
|
|
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 =
|
|
2137
|
-
if (!
|
|
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 =
|
|
2142
|
-
const rawContent =
|
|
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 (
|
|
2161
|
-
const existingContent =
|
|
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 =
|
|
2211
|
-
const targetDir =
|
|
2212
|
-
if (!
|
|
2213
|
-
|
|
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
|
-
|
|
2225
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2494
|
-
import { join as
|
|
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 (!
|
|
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
|
|
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") ||
|
|
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 =
|
|
2961
|
-
if (!
|
|
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 =
|
|
2975
|
-
if (!
|
|
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) =>
|
|
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 =
|
|
2996
|
-
if (!
|
|
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(
|
|
8048
|
+
var p19 = new Promise(function(resolve40, reject) {
|
|
7768
8049
|
pushCb(value, function(err, result) {
|
|
7769
8050
|
if (err) {
|
|
7770
8051
|
reject(err);
|
|
7771
8052
|
return;
|
|
7772
8053
|
}
|
|
7773
|
-
|
|
8054
|
+
resolve40(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(
|
|
8061
|
+
var p19 = new Promise(function(resolve40, reject) {
|
|
7781
8062
|
unshiftCb(value, function(err, result) {
|
|
7782
8063
|
if (err) {
|
|
7783
8064
|
reject(err);
|
|
7784
8065
|
return;
|
|
7785
8066
|
}
|
|
7786
|
-
|
|
8067
|
+
resolve40(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(
|
|
8074
|
+
var p19 = new Promise(function(resolve40) {
|
|
7794
8075
|
process.nextTick(function() {
|
|
7795
8076
|
if (queue.idle()) {
|
|
7796
|
-
|
|
8077
|
+
resolve40();
|
|
7797
8078
|
} else {
|
|
7798
8079
|
var previousDrain = queue.drain;
|
|
7799
8080
|
queue.drain = function() {
|
|
7800
8081
|
if (typeof previousDrain === "function") previousDrain();
|
|
7801
|
-
|
|
8082
|
+
resolve40();
|
|
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((
|
|
8568
|
+
return new Promise((resolve40, reject) => {
|
|
8288
8569
|
this._stat(filepath, this._fsStatSettings, (error, stats) => {
|
|
8289
|
-
return error === null ?
|
|
8570
|
+
return error === null ? resolve40(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((
|
|
8594
|
+
return new Promise((resolve40, reject) => {
|
|
8314
8595
|
this._walkAsync(root, options, (error, entries) => {
|
|
8315
8596
|
if (error === null) {
|
|
8316
|
-
|
|
8597
|
+
resolve40(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((
|
|
8607
|
+
return new Promise((resolve40, reject) => {
|
|
8327
8608
|
stream.once("error", reject);
|
|
8328
8609
|
stream.on("data", (entry) => entries.push(entry));
|
|
8329
|
-
stream.once("end", () =>
|
|
8610
|
+
stream.once("end", () => resolve40(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
|
|
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(
|
|
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
|
|
9183
|
-
import { join as
|
|
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 (!
|
|
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
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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) =>
|
|
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 =
|
|
9265
|
-
if (!
|
|
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(
|
|
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(
|
|
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 =
|
|
9306
|
-
if (!
|
|
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 =
|
|
9317
|
-
if (
|
|
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
|
|
9609
|
+
return existsSync7(join5(root, "turbo.json"));
|
|
9329
9610
|
}
|
|
9330
9611
|
function hasNx(root) {
|
|
9331
|
-
return
|
|
9612
|
+
return existsSync7(join5(root, "nx.json"));
|
|
9332
9613
|
}
|
|
9333
9614
|
function hasLerna(root) {
|
|
9334
|
-
return
|
|
9615
|
+
return existsSync7(join5(root, "lerna.json"));
|
|
9335
9616
|
}
|
|
9336
9617
|
function hasBazel(root) {
|
|
9337
|
-
return
|
|
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
|
|
9529
|
-
import { join as
|
|
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 =
|
|
9550
|
-
if (!
|
|
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
|
|
9638
|
-
import { resolve as
|
|
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 =
|
|
9805
|
-
return
|
|
10085
|
+
const abs = resolve6(projectRoot, first);
|
|
10086
|
+
return existsSync9(abs) ? abs : null;
|
|
9806
10087
|
}
|
|
9807
|
-
return
|
|
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 =
|
|
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
|
|
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
|
|
9934
|
-
import { join as
|
|
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(
|
|
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(
|
|
10041
|
-
pyprojectToml: tryReadToml(
|
|
10042
|
-
gemfile: tryReadString(
|
|
10043
|
-
cargoToml: tryReadToml(
|
|
10044
|
-
goMod: tryReadString(
|
|
10045
|
-
mixExs: tryReadString(
|
|
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(
|
|
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(
|
|
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(
|
|
10341
|
+
return tryReadString(join8(rootDir, "build.gradle.kts"));
|
|
10061
10342
|
}
|
|
10062
10343
|
if (presentFiles.has("build.gradle")) {
|
|
10063
|
-
return tryReadString(
|
|
10344
|
+
return tryReadString(join8(rootDir, "build.gradle"));
|
|
10064
10345
|
}
|
|
10065
10346
|
return void 0;
|
|
10066
10347
|
}
|
|
10067
10348
|
function tryReadString(path) {
|
|
10068
|
-
if (!
|
|
10349
|
+
if (!existsSync10(path)) return void 0;
|
|
10069
10350
|
try {
|
|
10070
|
-
return
|
|
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
|
|
10466
|
+
import { createHash as createHash3 } from "crypto";
|
|
10186
10467
|
import {
|
|
10187
|
-
mkdirSync as
|
|
10468
|
+
mkdirSync as mkdirSync4,
|
|
10188
10469
|
readdirSync as readdirSync8,
|
|
10189
|
-
readFileSync as
|
|
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
|
|
10198
|
-
import { dirname as
|
|
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 ??
|
|
10482
|
+
return process.env.MASSU_WASM_CACHE_DIR ?? join9(homedir3(), ".massu", "wasm-cache");
|
|
10202
10483
|
}
|
|
10203
10484
|
function getCachedPath(language, sha) {
|
|
10204
|
-
return
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
10609
|
+
mkdirSync4(dirname5(cachePath), { recursive: true, mode: 448 });
|
|
10329
10610
|
try {
|
|
10330
|
-
chmodSync(
|
|
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
|
|
11230
|
-
import { join as
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
12719
|
-
import { resolve as
|
|
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
|
|
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 =
|
|
12731
|
-
if (!
|
|
13011
|
+
const pkgPath = resolve7(projectRoot, "package.json");
|
|
13012
|
+
if (!existsSync11(pkgPath)) return result;
|
|
12732
13013
|
try {
|
|
12733
|
-
const pkg = JSON.parse(
|
|
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) =>
|
|
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 =
|
|
12778
|
-
if (
|
|
13058
|
+
const filePath = resolve7(projectRoot, file);
|
|
13059
|
+
if (existsSync11(filePath)) {
|
|
12779
13060
|
try {
|
|
12780
|
-
const content =
|
|
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 (
|
|
13068
|
+
if (existsSync11(resolve7(projectRoot, "alembic.ini"))) {
|
|
12788
13069
|
result.hasAlembic = true;
|
|
12789
|
-
if (
|
|
13070
|
+
if (existsSync11(resolve7(projectRoot, "alembic"))) {
|
|
12790
13071
|
result.alembicDir = "alembic";
|
|
12791
13072
|
}
|
|
12792
|
-
} else if (
|
|
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 =
|
|
12799
|
-
if (
|
|
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 (
|
|
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 =
|
|
12824
|
-
if (
|
|
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 (
|
|
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 =
|
|
13038
|
-
if (!
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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 (!
|
|
13142
|
-
const content =
|
|
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 =
|
|
13163
|
-
if (!
|
|
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 =
|
|
13174
|
-
if (!
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13226
|
-
|
|
13506
|
+
resolve7(__dirname2, "../../templates"),
|
|
13507
|
+
resolve7(__dirname2, "../../../templates")
|
|
13227
13508
|
];
|
|
13228
13509
|
for (const c2 of candidates) {
|
|
13229
|
-
if (
|
|
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 =
|
|
13239
|
-
if (!
|
|
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 =
|
|
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 =
|
|
13533
|
+
const mcpPath = resolve7(projectRoot, ".mcp.json");
|
|
13253
13534
|
let existing = {};
|
|
13254
|
-
if (
|
|
13535
|
+
if (existsSync11(mcpPath)) {
|
|
13255
13536
|
try {
|
|
13256
|
-
existing = JSON.parse(
|
|
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 =
|
|
13277
|
-
if (
|
|
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 =
|
|
13281
|
-
if (
|
|
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 =
|
|
13374
|
-
|
|
13375
|
-
|
|
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
|
-
|
|
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 =
|
|
13673
|
+
const memoryDir = resolve7(homedir4(), `.claude/projects/${encodedRoot}/memory`);
|
|
13401
13674
|
let created = false;
|
|
13402
|
-
if (!
|
|
13403
|
-
|
|
13675
|
+
if (!existsSync11(memoryDir)) {
|
|
13676
|
+
mkdirSync5(memoryDir, { recursive: true });
|
|
13404
13677
|
created = true;
|
|
13405
13678
|
}
|
|
13406
|
-
const memoryMdPath =
|
|
13679
|
+
const memoryMdPath = resolve7(memoryDir, "MEMORY.md");
|
|
13407
13680
|
let memoryMdCreated = false;
|
|
13408
|
-
if (!
|
|
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 =
|
|
13515
|
-
if (
|
|
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 =
|
|
13655
|
-
if (!
|
|
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
|
-
|
|
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 =
|
|
13697
|
-
const memFiles =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
14077
|
-
import { resolve as
|
|
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 =
|
|
14082
|
-
if (!
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
14416
|
+
const nodeModulesHooksDir = resolve8(projectRoot, "node_modules/@massu/core/dist/hooks");
|
|
14143
14417
|
let hooksDir = nodeModulesHooksDir;
|
|
14144
|
-
if (!
|
|
14145
|
-
const devHooksDir =
|
|
14146
|
-
if (
|
|
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 (!
|
|
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 =
|
|
14181
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
14281
|
-
if (!
|
|
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 =
|
|
14301
|
-
if (depth <= 2 && !
|
|
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 =
|
|
14397
|
-
if (!
|
|
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 =
|
|
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 =
|
|
14718
|
+
__dirname3 = dirname7(__filename3);
|
|
14444
14719
|
EXPECTED_HOOKS = [
|
|
14445
14720
|
"session-start.js",
|
|
14446
14721
|
"session-end.js",
|
|
@@ -14481,13 +14756,379 @@ 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
|
+
|
|
14886
|
+
// src/changelog-generator.ts
|
|
14887
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync12 } from "fs";
|
|
14888
|
+
import { resolve as resolve10 } from "path";
|
|
14889
|
+
function parseCommitsForPlanTokens(subjects) {
|
|
14890
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
14891
|
+
const maintenance = [];
|
|
14892
|
+
for (const subject of subjects) {
|
|
14893
|
+
const m3 = subject.match(PLAN_TOKEN_RE);
|
|
14894
|
+
if (m3 && m3[2]) {
|
|
14895
|
+
tokens.add(m3[2]);
|
|
14896
|
+
} else {
|
|
14897
|
+
maintenance.push(subject);
|
|
14898
|
+
}
|
|
14899
|
+
}
|
|
14900
|
+
return { tokens, maintenance };
|
|
14901
|
+
}
|
|
14902
|
+
function loadPlanSummaries(tokens, planDir) {
|
|
14903
|
+
const result = /* @__PURE__ */ new Map();
|
|
14904
|
+
if (tokens.size === 0) return result;
|
|
14905
|
+
if (!existsSync13(planDir)) {
|
|
14906
|
+
throw new Error(`Plan directory does not exist: ${planDir}`);
|
|
14907
|
+
}
|
|
14908
|
+
const files = readdirSync12(planDir).filter((f2) => f2.endsWith(".md"));
|
|
14909
|
+
for (const token of tokens) {
|
|
14910
|
+
let matchedFile = null;
|
|
14911
|
+
let content = "";
|
|
14912
|
+
for (const file of files) {
|
|
14913
|
+
const path = resolve10(planDir, file);
|
|
14914
|
+
const text18 = readFileSync13(path, "utf-8");
|
|
14915
|
+
const tokenRe = new RegExp(
|
|
14916
|
+
`^\\*\\*Plan Token\\*\\*:\\s*\`?${token.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\`?(\\s|$)`,
|
|
14917
|
+
"m"
|
|
14918
|
+
);
|
|
14919
|
+
if (tokenRe.test(text18)) {
|
|
14920
|
+
matchedFile = file;
|
|
14921
|
+
content = text18;
|
|
14922
|
+
break;
|
|
14923
|
+
}
|
|
14924
|
+
}
|
|
14925
|
+
if (!matchedFile) {
|
|
14926
|
+
throw new MissingPlanFileError(token);
|
|
14927
|
+
}
|
|
14928
|
+
const titleMatch = content.match(/^# (.+)$/m);
|
|
14929
|
+
const title = titleMatch ? titleMatch[1].trim() : token;
|
|
14930
|
+
const sectionRe = /^## Changelog Summary\s*\n([\s\S]*?)(?=\n## |\n---|\n# |$)/m;
|
|
14931
|
+
const sectionMatch = content.match(sectionRe);
|
|
14932
|
+
if (!sectionMatch || !sectionMatch[1].trim()) {
|
|
14933
|
+
throw new MissingChangelogSummaryError(token, matchedFile);
|
|
14934
|
+
}
|
|
14935
|
+
const summary = sectionMatch[1].trim();
|
|
14936
|
+
result.set(token, { title, summary });
|
|
14937
|
+
}
|
|
14938
|
+
return result;
|
|
14939
|
+
}
|
|
14940
|
+
function generateChangelogEntry(opts) {
|
|
14941
|
+
const parts = [];
|
|
14942
|
+
parts.push(`## [${opts.version}] - ${opts.date}
|
|
14943
|
+
`);
|
|
14944
|
+
parts.push("");
|
|
14945
|
+
for (const [, planSum] of opts.planSummaries) {
|
|
14946
|
+
parts.push(planSum.summary);
|
|
14947
|
+
parts.push("");
|
|
14948
|
+
}
|
|
14949
|
+
if (opts.maintenance.length > 0) {
|
|
14950
|
+
parts.push("### Maintenance");
|
|
14951
|
+
parts.push("");
|
|
14952
|
+
for (const subject of opts.maintenance) {
|
|
14953
|
+
parts.push(`- ${subject}`);
|
|
14954
|
+
}
|
|
14955
|
+
parts.push("");
|
|
14956
|
+
}
|
|
14957
|
+
return parts.join("\n") + "\n";
|
|
14958
|
+
}
|
|
14959
|
+
function findCoverageGaps(entryText, tokens) {
|
|
14960
|
+
const gaps = [];
|
|
14961
|
+
for (const token of tokens) {
|
|
14962
|
+
if (!entryText.includes(token)) {
|
|
14963
|
+
gaps.push(token);
|
|
14964
|
+
}
|
|
14965
|
+
}
|
|
14966
|
+
return gaps;
|
|
14967
|
+
}
|
|
14968
|
+
var PLAN_TOKEN_RE, MissingPlanFileError, MissingChangelogSummaryError;
|
|
14969
|
+
var init_changelog_generator = __esm({
|
|
14970
|
+
"src/changelog-generator.ts"() {
|
|
14971
|
+
"use strict";
|
|
14972
|
+
PLAN_TOKEN_RE = /^(feat|fix|chore|docs)\((plan-[a-z0-9._-]+)\)/;
|
|
14973
|
+
MissingPlanFileError = class extends Error {
|
|
14974
|
+
constructor(token) {
|
|
14975
|
+
super(`No plan file found in plans directory matching Plan Token: ${token}`);
|
|
14976
|
+
this.name = "MissingPlanFileError";
|
|
14977
|
+
}
|
|
14978
|
+
};
|
|
14979
|
+
MissingChangelogSummaryError = class extends Error {
|
|
14980
|
+
constructor(token, planFile) {
|
|
14981
|
+
super(`Plan file ${planFile} for token ${token} has no '## Changelog Summary' section`);
|
|
14982
|
+
this.name = "MissingChangelogSummaryError";
|
|
14983
|
+
}
|
|
14984
|
+
};
|
|
14985
|
+
}
|
|
14986
|
+
});
|
|
14987
|
+
|
|
14988
|
+
// src/commands/changelog.ts
|
|
14989
|
+
var changelog_exports = {};
|
|
14990
|
+
__export(changelog_exports, {
|
|
14991
|
+
handleChangelogSubcommand: () => handleChangelogSubcommand,
|
|
14992
|
+
printChangelogHelp: () => printChangelogHelp
|
|
14993
|
+
});
|
|
14994
|
+
import { execSync } from "child_process";
|
|
14995
|
+
import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
|
|
14996
|
+
import { resolve as resolve11 } from "path";
|
|
14997
|
+
function resolveRepoRoot() {
|
|
14998
|
+
try {
|
|
14999
|
+
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
15000
|
+
} catch {
|
|
15001
|
+
return process.cwd();
|
|
15002
|
+
}
|
|
15003
|
+
}
|
|
15004
|
+
function getLastTag() {
|
|
15005
|
+
try {
|
|
15006
|
+
return execSync("git describe --tags --abbrev=0", { encoding: "utf-8" }).trim();
|
|
15007
|
+
} catch {
|
|
15008
|
+
return null;
|
|
15009
|
+
}
|
|
15010
|
+
}
|
|
15011
|
+
function getCommitSubjects(range) {
|
|
15012
|
+
try {
|
|
15013
|
+
const out = execSync(`git log ${range} --pretty=format:%s`, { encoding: "utf-8" });
|
|
15014
|
+
return out.split("\n").filter((s) => s.length > 0);
|
|
15015
|
+
} catch {
|
|
15016
|
+
return [];
|
|
15017
|
+
}
|
|
15018
|
+
}
|
|
15019
|
+
function getCurrentVersion(repoRoot) {
|
|
15020
|
+
const pkgPath = resolve11(repoRoot, "packages/core/package.json");
|
|
15021
|
+
if (!existsSync14(pkgPath)) return "0.0.0";
|
|
15022
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
|
|
15023
|
+
return pkg.version || "0.0.0";
|
|
15024
|
+
}
|
|
15025
|
+
function todayDate() {
|
|
15026
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
15027
|
+
}
|
|
15028
|
+
function getLatestChangelogEntryBody(repoRoot) {
|
|
15029
|
+
const path = resolve11(repoRoot, "CHANGELOG.md");
|
|
15030
|
+
if (!existsSync14(path)) return "";
|
|
15031
|
+
const content = readFileSync14(path, "utf-8");
|
|
15032
|
+
const m3 = content.match(/^## \[[\d.]+\][^\n]*\n([\s\S]*?)(?=\n## \[|$)/m);
|
|
15033
|
+
return m3 ? m3[1] : "";
|
|
15034
|
+
}
|
|
15035
|
+
async function handleChangelogSubcommand(args2) {
|
|
15036
|
+
const sub = args2[0];
|
|
15037
|
+
const repoRoot = resolveRepoRoot();
|
|
15038
|
+
const planDir = resolve11(repoRoot, "docs/plans");
|
|
15039
|
+
switch (sub) {
|
|
15040
|
+
case "generate": {
|
|
15041
|
+
const lastTag = getLastTag();
|
|
15042
|
+
const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
|
|
15043
|
+
const subjects = getCommitSubjects(range);
|
|
15044
|
+
const { tokens, maintenance } = parseCommitsForPlanTokens(subjects);
|
|
15045
|
+
let planSummaries;
|
|
15046
|
+
try {
|
|
15047
|
+
planSummaries = loadPlanSummaries(tokens, planDir);
|
|
15048
|
+
} catch (err) {
|
|
15049
|
+
if (err instanceof MissingPlanFileError || err instanceof MissingChangelogSummaryError) {
|
|
15050
|
+
process.stderr.write(`changelog generate: ${err.message}
|
|
15051
|
+
`);
|
|
15052
|
+
return { exitCode: 2 };
|
|
15053
|
+
}
|
|
15054
|
+
throw err;
|
|
15055
|
+
}
|
|
15056
|
+
const entry = generateChangelogEntry({
|
|
15057
|
+
version: getCurrentVersion(repoRoot),
|
|
15058
|
+
date: todayDate(),
|
|
15059
|
+
planSummaries,
|
|
15060
|
+
maintenance
|
|
15061
|
+
});
|
|
15062
|
+
process.stdout.write(entry);
|
|
15063
|
+
return { exitCode: 0 };
|
|
15064
|
+
}
|
|
15065
|
+
case "verify": {
|
|
15066
|
+
const lastTag = getLastTag();
|
|
15067
|
+
const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
|
|
15068
|
+
const subjects = getCommitSubjects(range);
|
|
15069
|
+
const { tokens } = parseCommitsForPlanTokens(subjects);
|
|
15070
|
+
const entryBody = getLatestChangelogEntryBody(repoRoot);
|
|
15071
|
+
const gaps = findCoverageGaps(entryBody, tokens);
|
|
15072
|
+
if (gaps.length === 0) {
|
|
15073
|
+
process.stdout.write("All plan-tokens referenced.\n");
|
|
15074
|
+
return { exitCode: 0 };
|
|
15075
|
+
}
|
|
15076
|
+
for (const t of gaps) {
|
|
15077
|
+
process.stderr.write(`gap: ${t}
|
|
15078
|
+
`);
|
|
15079
|
+
}
|
|
15080
|
+
return { exitCode: 1 };
|
|
15081
|
+
}
|
|
15082
|
+
case "--help":
|
|
15083
|
+
case "-h":
|
|
15084
|
+
case void 0: {
|
|
15085
|
+
printChangelogHelp();
|
|
15086
|
+
return { exitCode: 0 };
|
|
15087
|
+
}
|
|
15088
|
+
default: {
|
|
15089
|
+
process.stderr.write(`massu: unknown changelog subcommand: ${sub}
|
|
15090
|
+
`);
|
|
15091
|
+
printChangelogHelp();
|
|
15092
|
+
return { exitCode: 1 };
|
|
15093
|
+
}
|
|
15094
|
+
}
|
|
15095
|
+
}
|
|
15096
|
+
function printChangelogHelp() {
|
|
15097
|
+
process.stdout.write(`
|
|
15098
|
+
massu changelog <subcommand>
|
|
15099
|
+
|
|
15100
|
+
Subcommands:
|
|
15101
|
+
generate Emit a draft CHANGELOG.md entry to stdout. Reads commit subjects
|
|
15102
|
+
since the last git tag, groups by (plan-<token>) paren-notation,
|
|
15103
|
+
looks up each plan file's ## Changelog Summary section, and emits
|
|
15104
|
+
a Keep-a-Changelog 1.1.0 entry. Operator pipes/copies into
|
|
15105
|
+
CHANGELOG.md (no forced overwrite).
|
|
15106
|
+
|
|
15107
|
+
verify Read-only check that the latest CHANGELOG.md entry references
|
|
15108
|
+
every plan-token in commits since the last tag. Exit 0 if clean,
|
|
15109
|
+
exit 1 with one 'gap: <token>' per missing.
|
|
15110
|
+
|
|
15111
|
+
Examples:
|
|
15112
|
+
npx massu changelog generate > /tmp/draft-entry.md
|
|
15113
|
+
npx massu changelog verify
|
|
15114
|
+
|
|
15115
|
+
Documentation: https://massu.ai/docs/reference/cli-reference#massu-changelog
|
|
15116
|
+
`);
|
|
15117
|
+
}
|
|
15118
|
+
var init_changelog = __esm({
|
|
15119
|
+
"src/commands/changelog.ts"() {
|
|
15120
|
+
"use strict";
|
|
15121
|
+
init_changelog_generator();
|
|
15122
|
+
}
|
|
15123
|
+
});
|
|
15124
|
+
|
|
14484
15125
|
// src/commands/show-template.ts
|
|
14485
15126
|
var show_template_exports = {};
|
|
14486
15127
|
__export(show_template_exports, {
|
|
14487
15128
|
runShowTemplate: () => runShowTemplate
|
|
14488
15129
|
});
|
|
14489
|
-
import { existsSync as
|
|
14490
|
-
import { resolve as
|
|
15130
|
+
import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
|
|
15131
|
+
import { resolve as resolve12 } from "path";
|
|
14491
15132
|
function normalizeBaseName(input) {
|
|
14492
15133
|
return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
|
|
14493
15134
|
}
|
|
@@ -14515,14 +15156,14 @@ async function runShowTemplate(args2) {
|
|
|
14515
15156
|
return;
|
|
14516
15157
|
}
|
|
14517
15158
|
const suffix = choice.kind === "hit" ? choice.suffix : "";
|
|
14518
|
-
const file = suffix === "" ?
|
|
14519
|
-
if (!
|
|
15159
|
+
const file = suffix === "" ? resolve12(sourceDir, `${baseName}.md`) : resolve12(sourceDir, `${baseName}${suffix}.md`);
|
|
15160
|
+
if (!existsSync15(file)) {
|
|
14520
15161
|
process.stderr.write(`massu: resolved template "${file}" no longer exists
|
|
14521
15162
|
`);
|
|
14522
15163
|
process.exit(1);
|
|
14523
15164
|
return;
|
|
14524
15165
|
}
|
|
14525
|
-
process.stdout.write(
|
|
15166
|
+
process.stdout.write(readFileSync15(file, "utf-8"));
|
|
14526
15167
|
}
|
|
14527
15168
|
var init_show_template = __esm({
|
|
14528
15169
|
"src/commands/show-template.ts"() {
|
|
@@ -14575,12 +15216,12 @@ var init_passthrough = __esm({
|
|
|
14575
15216
|
});
|
|
14576
15217
|
|
|
14577
15218
|
// src/lib/fileLock.ts
|
|
14578
|
-
import { mkdirSync as
|
|
14579
|
-
import { dirname as
|
|
15219
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync16, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
15220
|
+
import { dirname as dirname8 } from "path";
|
|
14580
15221
|
import * as lockfile from "proper-lockfile";
|
|
14581
15222
|
function readLockHolderPid(lockPath) {
|
|
14582
15223
|
try {
|
|
14583
|
-
const raw =
|
|
15224
|
+
const raw = readFileSync16(`${lockPath}.pid`, "utf-8").trim();
|
|
14584
15225
|
const pid = Number.parseInt(raw, 10);
|
|
14585
15226
|
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
14586
15227
|
return pid;
|
|
@@ -14599,7 +15240,7 @@ function busyWaitSync(ms) {
|
|
|
14599
15240
|
Atomics.wait(view, 0, 0, ms);
|
|
14600
15241
|
}
|
|
14601
15242
|
function withFileLockSync(lockPath, fn, opts = {}) {
|
|
14602
|
-
|
|
15243
|
+
mkdirSync6(dirname8(lockPath), { recursive: true });
|
|
14603
15244
|
const staleMs = opts.staleMs ?? 3e4;
|
|
14604
15245
|
const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
|
|
14605
15246
|
const pollIntervalMs = opts.pollIntervalMs ?? 100;
|
|
@@ -14667,9 +15308,9 @@ var init_fileLock = __esm({
|
|
|
14667
15308
|
});
|
|
14668
15309
|
|
|
14669
15310
|
// src/lib/installLock.ts
|
|
14670
|
-
import { resolve as
|
|
15311
|
+
import { resolve as resolve13 } from "path";
|
|
14671
15312
|
function withInstallLock(projectRoot, fn, opts = {}) {
|
|
14672
|
-
const lockPath =
|
|
15313
|
+
const lockPath = resolve13(projectRoot, ".massu", "installAll.lock");
|
|
14673
15314
|
return withFileLockSync(
|
|
14674
15315
|
lockPath,
|
|
14675
15316
|
fn,
|
|
@@ -14705,8 +15346,8 @@ __export(config_refresh_exports, {
|
|
|
14705
15346
|
mergeRefresh: () => mergeRefresh,
|
|
14706
15347
|
runConfigRefresh: () => runConfigRefresh
|
|
14707
15348
|
});
|
|
14708
|
-
import { existsSync as
|
|
14709
|
-
import { resolve as
|
|
15349
|
+
import { existsSync as existsSync16, readFileSync as readFileSync17, rmSync as rmSync4 } from "fs";
|
|
15350
|
+
import { resolve as resolve14 } from "path";
|
|
14710
15351
|
import { parse as parseYaml5 } from "yaml";
|
|
14711
15352
|
function flatten(obj, prefix3 = "") {
|
|
14712
15353
|
const out = {};
|
|
@@ -14834,17 +15475,17 @@ function renderDiff(diff) {
|
|
|
14834
15475
|
}
|
|
14835
15476
|
async function runConfigRefresh(opts = {}) {
|
|
14836
15477
|
const cwd = opts.cwd ?? process.cwd();
|
|
14837
|
-
const configPath =
|
|
15478
|
+
const configPath = resolve14(cwd, "massu.config.yaml");
|
|
14838
15479
|
const log = opts.silent ? () => {
|
|
14839
15480
|
} : (s) => process.stdout.write(s);
|
|
14840
|
-
if (!
|
|
15481
|
+
if (!existsSync16(configPath)) {
|
|
14841
15482
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
14842
15483
|
if (!opts.silent) process.stderr.write(message + "\n");
|
|
14843
15484
|
return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
|
|
14844
15485
|
}
|
|
14845
15486
|
let existing;
|
|
14846
15487
|
try {
|
|
14847
|
-
const content =
|
|
15488
|
+
const content = readFileSync17(configPath, "utf-8");
|
|
14848
15489
|
const parsed = parseYaml5(content);
|
|
14849
15490
|
if (!parsed || typeof parsed !== "object") {
|
|
14850
15491
|
throw new Error("config is not a YAML object");
|
|
@@ -14917,8 +15558,8 @@ async function runConfigRefresh(opts = {}) {
|
|
|
14917
15558
|
`);
|
|
14918
15559
|
const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
|
|
14919
15560
|
if (stackResolved) {
|
|
14920
|
-
const placeholderPath =
|
|
14921
|
-
if (
|
|
15561
|
+
const placeholderPath = resolve14(installResult.claudeDir, "commands", "_massu-needs-stack.md");
|
|
15562
|
+
if (existsSync16(placeholderPath)) {
|
|
14922
15563
|
try {
|
|
14923
15564
|
rmSync4(placeholderPath, { force: true });
|
|
14924
15565
|
log("Removed _massu-needs-stack.md (stack now declared).\n");
|
|
@@ -15027,12 +15668,12 @@ var init_gitToplevel = __esm({
|
|
|
15027
15668
|
});
|
|
15028
15669
|
|
|
15029
15670
|
// src/watch/lockfile-detector.ts
|
|
15030
|
-
import { existsSync as
|
|
15031
|
-
import { resolve as
|
|
15671
|
+
import { existsSync as existsSync17, statSync as statSync8 } from "fs";
|
|
15672
|
+
import { resolve as resolve15 } from "path";
|
|
15032
15673
|
function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
|
|
15033
15674
|
for (const lf of KNOWN_LOCKFILES) {
|
|
15034
|
-
const p19 =
|
|
15035
|
-
if (!
|
|
15675
|
+
const p19 = resolve15(projectRoot, lf);
|
|
15676
|
+
if (!existsSync17(p19)) continue;
|
|
15036
15677
|
try {
|
|
15037
15678
|
const stat = statSync8(p19);
|
|
15038
15679
|
const delta = now - stat.mtimeMs;
|
|
@@ -15045,7 +15686,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
|
|
|
15045
15686
|
function gitMidOperation(projectRoot) {
|
|
15046
15687
|
const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
|
|
15047
15688
|
for (const s of sentinels) {
|
|
15048
|
-
if (
|
|
15689
|
+
if (existsSync17(resolve15(projectRoot, ".git", s))) return true;
|
|
15049
15690
|
}
|
|
15050
15691
|
return false;
|
|
15051
15692
|
}
|
|
@@ -15242,18 +15883,18 @@ var init_paths = __esm({
|
|
|
15242
15883
|
});
|
|
15243
15884
|
|
|
15244
15885
|
// src/watch/state.ts
|
|
15245
|
-
import { closeSync as closeSync3, existsSync as
|
|
15246
|
-
import { dirname as
|
|
15886
|
+
import { closeSync as closeSync3, existsSync as existsSync18, fsyncSync as fsyncSync3, mkdirSync as mkdirSync7, openSync as openSync3, readFileSync as readFileSync18, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync4, writeSync as writeSync3 } from "fs";
|
|
15887
|
+
import { dirname as dirname9, resolve as resolve16 } from "path";
|
|
15247
15888
|
function watchStatePath(projectRoot) {
|
|
15248
|
-
return
|
|
15889
|
+
return resolve16(projectRoot, ".massu", "watch-state.json");
|
|
15249
15890
|
}
|
|
15250
15891
|
function backupStatePath(projectRoot) {
|
|
15251
|
-
return
|
|
15892
|
+
return resolve16(projectRoot, ".massu", "watch-state.v0.bak.json");
|
|
15252
15893
|
}
|
|
15253
15894
|
function readState(projectRoot) {
|
|
15254
15895
|
const path = watchStatePath(projectRoot);
|
|
15255
|
-
if (!
|
|
15256
|
-
const content =
|
|
15896
|
+
if (!existsSync18(path)) return { ...DEFAULT_STATE };
|
|
15897
|
+
const content = readFileSync18(path, "utf-8");
|
|
15257
15898
|
let raw;
|
|
15258
15899
|
try {
|
|
15259
15900
|
raw = JSON.parse(content);
|
|
@@ -15295,14 +15936,14 @@ function readState(projectRoot) {
|
|
|
15295
15936
|
}
|
|
15296
15937
|
function archiveCorrupt(projectRoot, content) {
|
|
15297
15938
|
const bak = backupStatePath(projectRoot);
|
|
15298
|
-
|
|
15939
|
+
mkdirSync7(dirname9(bak), { recursive: true });
|
|
15299
15940
|
writeFileSync4(bak, content, "utf-8");
|
|
15300
15941
|
}
|
|
15301
15942
|
function writeStateAtomic(projectRoot, state) {
|
|
15302
15943
|
const path = watchStatePath(projectRoot);
|
|
15303
15944
|
writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
|
|
15304
15945
|
const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
|
|
15305
|
-
|
|
15946
|
+
mkdirSync7(dirname9(path), { recursive: true });
|
|
15306
15947
|
let renamed = false;
|
|
15307
15948
|
try {
|
|
15308
15949
|
const fd = openSync3(tmp, "w");
|
|
@@ -15316,7 +15957,7 @@ function writeStateAtomic(projectRoot, state) {
|
|
|
15316
15957
|
renameSync4(tmp, path);
|
|
15317
15958
|
renamed = true;
|
|
15318
15959
|
} finally {
|
|
15319
|
-
if (!renamed &&
|
|
15960
|
+
if (!renamed && existsSync18(tmp)) {
|
|
15320
15961
|
try {
|
|
15321
15962
|
rmSync5(tmp, { force: true });
|
|
15322
15963
|
} catch {
|
|
@@ -15596,8 +16237,8 @@ __export(watch_exports, {
|
|
|
15596
16237
|
runWatch: () => runWatch
|
|
15597
16238
|
});
|
|
15598
16239
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
15599
|
-
import { basename as basename5, dirname as
|
|
15600
|
-
import { appendFileSync, existsSync as
|
|
16240
|
+
import { basename as basename5, dirname as dirname10, resolve as resolve17 } from "path";
|
|
16241
|
+
import { appendFileSync, existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync19 } from "fs";
|
|
15601
16242
|
function parseFlags(args2) {
|
|
15602
16243
|
const out = {
|
|
15603
16244
|
foreground: false,
|
|
@@ -15622,8 +16263,8 @@ function parseFlags(args2) {
|
|
|
15622
16263
|
}
|
|
15623
16264
|
function findClaudeBg() {
|
|
15624
16265
|
const home = process.env.HOME ?? "";
|
|
15625
|
-
const fixed = home ?
|
|
15626
|
-
if (fixed &&
|
|
16266
|
+
const fixed = home ? resolve17(home, ".claude", "bin", "claude-bg") : null;
|
|
16267
|
+
if (fixed && existsSync19(fixed)) return fixed;
|
|
15627
16268
|
const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
|
|
15628
16269
|
if (which.status === 0 && which.stdout) {
|
|
15629
16270
|
const p19 = which.stdout.trim();
|
|
@@ -15684,7 +16325,7 @@ async function runWatch(args2) {
|
|
|
15684
16325
|
}
|
|
15685
16326
|
function runStatus(root) {
|
|
15686
16327
|
const path = watchStatePath(root);
|
|
15687
|
-
if (!
|
|
16328
|
+
if (!existsSync19(path)) {
|
|
15688
16329
|
process.stdout.write("massu watch: not running (no state file)\n");
|
|
15689
16330
|
return { exitCode: 0 };
|
|
15690
16331
|
}
|
|
@@ -15766,7 +16407,7 @@ async function runForeground(root) {
|
|
|
15766
16407
|
}
|
|
15767
16408
|
throw err;
|
|
15768
16409
|
}
|
|
15769
|
-
return new Promise((
|
|
16410
|
+
return new Promise((resolve40) => {
|
|
15770
16411
|
const shutdown = async () => {
|
|
15771
16412
|
if (stopped) return;
|
|
15772
16413
|
stopped = true;
|
|
@@ -15776,7 +16417,7 @@ async function runForeground(root) {
|
|
|
15776
16417
|
process.chdir(priorCwd);
|
|
15777
16418
|
} catch {
|
|
15778
16419
|
}
|
|
15779
|
-
|
|
16420
|
+
resolve40({ exitCode: 0 });
|
|
15780
16421
|
};
|
|
15781
16422
|
process.on("SIGINT", () => {
|
|
15782
16423
|
void shutdown();
|
|
@@ -15837,23 +16478,23 @@ async function runOnQuiescent(projectRoot) {
|
|
|
15837
16478
|
);
|
|
15838
16479
|
}
|
|
15839
16480
|
function refreshLogPath(projectRoot) {
|
|
15840
|
-
return
|
|
16481
|
+
return resolve17(projectRoot, ".massu", "refresh-log.jsonl");
|
|
15841
16482
|
}
|
|
15842
16483
|
function appendRefreshLog(projectRoot, event) {
|
|
15843
16484
|
const path = refreshLogPath(projectRoot);
|
|
15844
16485
|
try {
|
|
15845
|
-
|
|
16486
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
15846
16487
|
appendFileSync(path, JSON.stringify(event) + "\n", "utf-8");
|
|
15847
16488
|
} catch {
|
|
15848
16489
|
}
|
|
15849
16490
|
}
|
|
15850
16491
|
function readRefreshLog(projectRoot, limit = 10, opts = {}) {
|
|
15851
16492
|
const path = refreshLogPath(projectRoot);
|
|
15852
|
-
if (!
|
|
16493
|
+
if (!existsSync19(path)) return [];
|
|
15853
16494
|
const warn = opts.warn ?? ((s) => {
|
|
15854
16495
|
process.stderr.write(s);
|
|
15855
16496
|
});
|
|
15856
|
-
const lines =
|
|
16497
|
+
const lines = readFileSync19(path, "utf-8").split("\n").filter(Boolean);
|
|
15857
16498
|
const tail = lines.slice(-limit);
|
|
15858
16499
|
const out = [];
|
|
15859
16500
|
let corrupt = 0;
|
|
@@ -15926,26 +16567,26 @@ var init_refresh_log = __esm({
|
|
|
15926
16567
|
import {
|
|
15927
16568
|
chmodSync as chmodSync3,
|
|
15928
16569
|
closeSync as closeSync4,
|
|
15929
|
-
existsSync as
|
|
16570
|
+
existsSync as existsSync20,
|
|
15930
16571
|
fsyncSync as fsyncSync4,
|
|
15931
|
-
mkdirSync as
|
|
16572
|
+
mkdirSync as mkdirSync9,
|
|
15932
16573
|
openSync as openSync4,
|
|
15933
16574
|
renameSync as renameSync5,
|
|
15934
16575
|
rmSync as rmSync6,
|
|
15935
16576
|
statSync as statSync9,
|
|
15936
16577
|
writeSync as writeSync4
|
|
15937
16578
|
} from "node:fs";
|
|
15938
|
-
import { dirname as
|
|
16579
|
+
import { dirname as dirname11 } from "node:path";
|
|
15939
16580
|
function atomicWrite(path, content, opts = {}) {
|
|
15940
16581
|
const tmpPath = `${path}.tmp`;
|
|
15941
|
-
const parentDir =
|
|
16582
|
+
const parentDir = dirname11(path);
|
|
15942
16583
|
try {
|
|
15943
|
-
if (!
|
|
16584
|
+
if (!existsSync20(parentDir)) {
|
|
15944
16585
|
const mkdirOpts = { recursive: true };
|
|
15945
16586
|
if (opts.ensureParentDirMode !== void 0) {
|
|
15946
16587
|
mkdirOpts.mode = opts.ensureParentDirMode;
|
|
15947
16588
|
}
|
|
15948
|
-
|
|
16589
|
+
mkdirSync9(parentDir, mkdirOpts);
|
|
15949
16590
|
}
|
|
15950
16591
|
const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
15951
16592
|
const openMode = opts.mode ?? 420;
|
|
@@ -15962,7 +16603,7 @@ function atomicWrite(path, content, opts = {}) {
|
|
|
15962
16603
|
renameSync5(tmpPath, path);
|
|
15963
16604
|
return { written: true };
|
|
15964
16605
|
} catch (err) {
|
|
15965
|
-
if (
|
|
16606
|
+
if (existsSync20(tmpPath)) {
|
|
15966
16607
|
try {
|
|
15967
16608
|
rmSync6(tmpPath, { force: true });
|
|
15968
16609
|
} catch {
|
|
@@ -16042,10 +16683,10 @@ var init_registry_pubkey_generated = __esm({
|
|
|
16042
16683
|
});
|
|
16043
16684
|
|
|
16044
16685
|
// src/security/adapter-verifier.ts
|
|
16045
|
-
import { createHash as
|
|
16686
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
16046
16687
|
import nacl from "tweetnacl";
|
|
16047
|
-
function
|
|
16048
|
-
return
|
|
16688
|
+
function sha256Hex2(bytes) {
|
|
16689
|
+
return createHash6("sha256").update(bytes).digest("hex");
|
|
16049
16690
|
}
|
|
16050
16691
|
function reviver(key, value) {
|
|
16051
16692
|
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
@@ -16088,7 +16729,7 @@ function verifyManifest(input) {
|
|
|
16088
16729
|
if (manifestBytes.length === 0) {
|
|
16089
16730
|
return { ok: false, reason: "manifest_b64 decoded to zero bytes" };
|
|
16090
16731
|
}
|
|
16091
|
-
const computedSha =
|
|
16732
|
+
const computedSha = sha256Hex2(manifestBytes);
|
|
16092
16733
|
if (computedSha !== envelope.manifest_sha256) {
|
|
16093
16734
|
return {
|
|
16094
16735
|
ok: false,
|
|
@@ -16142,10 +16783,10 @@ function verifyManifest(input) {
|
|
|
16142
16783
|
if (!sigOk) {
|
|
16143
16784
|
return {
|
|
16144
16785
|
ok: false,
|
|
16145
|
-
reason: `Ed25519 signature verification failed against bundled public key (fingerprint ${
|
|
16786
|
+
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
16787
|
};
|
|
16147
16788
|
}
|
|
16148
|
-
const expectedKeyId =
|
|
16789
|
+
const expectedKeyId = sha256Hex2(publicKey);
|
|
16149
16790
|
if (envelope.signing_key_id !== expectedKeyId) {
|
|
16150
16791
|
return {
|
|
16151
16792
|
ok: false,
|
|
@@ -16305,24 +16946,24 @@ var init_fetcher = __esm({
|
|
|
16305
16946
|
});
|
|
16306
16947
|
|
|
16307
16948
|
// src/security/manifest-cache.ts
|
|
16308
|
-
import { existsSync as
|
|
16309
|
-
import { homedir as
|
|
16310
|
-
import { resolve as
|
|
16949
|
+
import { existsSync as existsSync21, readFileSync as readFileSync20, statSync as statSync10 } from "node:fs";
|
|
16950
|
+
import { homedir as homedir5 } from "node:os";
|
|
16951
|
+
import { resolve as resolve18 } from "node:path";
|
|
16311
16952
|
import { z as z4 } from "zod";
|
|
16312
16953
|
function defaultCachePaths() {
|
|
16313
|
-
const dir =
|
|
16954
|
+
const dir = resolve18(homedir5(), ".massu");
|
|
16314
16955
|
return {
|
|
16315
|
-
cachePath:
|
|
16316
|
-
lockPath:
|
|
16956
|
+
cachePath: resolve18(dir, "adapter-manifest.json"),
|
|
16957
|
+
lockPath: resolve18(dir, ".adapter-manifest.lock")
|
|
16317
16958
|
};
|
|
16318
16959
|
}
|
|
16319
16960
|
function loadCachedManifest(paths = defaultCachePaths()) {
|
|
16320
|
-
if (!
|
|
16961
|
+
if (!existsSync21(paths.cachePath)) {
|
|
16321
16962
|
return { kind: "absent" };
|
|
16322
16963
|
}
|
|
16323
16964
|
let raw;
|
|
16324
16965
|
try {
|
|
16325
|
-
const content =
|
|
16966
|
+
const content = readFileSync20(paths.cachePath, "utf-8");
|
|
16326
16967
|
raw = JSON.parse(content);
|
|
16327
16968
|
} catch (err) {
|
|
16328
16969
|
return {
|
|
@@ -16511,15 +17152,15 @@ var init_adapter_origin = __esm({
|
|
|
16511
17152
|
});
|
|
16512
17153
|
|
|
16513
17154
|
// src/security/local-fingerprint.ts
|
|
16514
|
-
import { existsSync as
|
|
16515
|
-
import { homedir as
|
|
16516
|
-
import { resolve as
|
|
16517
|
-
import { createHash as
|
|
17155
|
+
import { existsSync as existsSync22, readFileSync as readFileSync21, lstatSync as lstatSync5 } from "node:fs";
|
|
17156
|
+
import { homedir as homedir6 } from "node:os";
|
|
17157
|
+
import { resolve as resolve19, isAbsolute } from "node:path";
|
|
17158
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
16518
17159
|
import { z as z5 } from "zod";
|
|
16519
17160
|
function computeLocalFingerprint(localPaths, projectRoot) {
|
|
16520
17161
|
const tuples = [];
|
|
16521
17162
|
for (const p19 of localPaths) {
|
|
16522
|
-
const abs = isAbsolute(p19) ? p19 :
|
|
17163
|
+
const abs = isAbsolute(p19) ? p19 : resolve19(projectRoot, p19);
|
|
16523
17164
|
let contentTag;
|
|
16524
17165
|
try {
|
|
16525
17166
|
const lst = lstatSync5(abs);
|
|
@@ -16528,7 +17169,7 @@ function computeLocalFingerprint(localPaths, projectRoot) {
|
|
|
16528
17169
|
} else if (!lst.isFile()) {
|
|
16529
17170
|
contentTag = "<not-a-file>";
|
|
16530
17171
|
} else {
|
|
16531
|
-
contentTag =
|
|
17172
|
+
contentTag = createHash7("sha256").update(readFileSync21(abs)).digest("hex");
|
|
16532
17173
|
}
|
|
16533
17174
|
} catch {
|
|
16534
17175
|
contentTag = "<missing>";
|
|
@@ -16537,13 +17178,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
|
|
|
16537
17178
|
}
|
|
16538
17179
|
tuples.sort((a2, b2) => a2.path < b2.path ? -1 : a2.path > b2.path ? 1 : 0);
|
|
16539
17180
|
const canonical = JSON.stringify(tuples);
|
|
16540
|
-
return
|
|
17181
|
+
return createHash7("sha256").update(canonical).digest("hex");
|
|
16541
17182
|
}
|
|
16542
17183
|
function readFingerprintSentinel(path = FINGERPRINT_PATH) {
|
|
16543
|
-
if (!
|
|
17184
|
+
if (!existsSync22(path)) return null;
|
|
16544
17185
|
let raw;
|
|
16545
17186
|
try {
|
|
16546
|
-
raw = JSON.parse(
|
|
17187
|
+
raw = JSON.parse(readFileSync21(path, "utf-8"));
|
|
16547
17188
|
} catch {
|
|
16548
17189
|
return null;
|
|
16549
17190
|
}
|
|
@@ -16591,7 +17232,7 @@ var init_local_fingerprint = __esm({
|
|
|
16591
17232
|
"use strict";
|
|
16592
17233
|
init_atomic_write();
|
|
16593
17234
|
init_manifest_schema();
|
|
16594
|
-
FINGERPRINT_PATH =
|
|
17235
|
+
FINGERPRINT_PATH = resolve19(homedir6(), ".massu", "adapters-local-fingerprint.json");
|
|
16595
17236
|
FingerprintSentinelSchema = z5.object({
|
|
16596
17237
|
fingerprint: z5.string().regex(/^[0-9a-f]{64}$/),
|
|
16597
17238
|
source: z5.enum(["cli", "cli-resync"]),
|
|
@@ -16607,15 +17248,15 @@ var init_local_fingerprint = __esm({
|
|
|
16607
17248
|
});
|
|
16608
17249
|
|
|
16609
17250
|
// src/security/install-tracking.ts
|
|
16610
|
-
import { readFileSync as
|
|
16611
|
-
import { join as
|
|
16612
|
-
import { homedir as
|
|
16613
|
-
import { resolve as
|
|
16614
|
-
import { createHash as
|
|
17251
|
+
import { readFileSync as readFileSync22, readdirSync as readdirSync13, lstatSync as lstatSync6, existsSync as existsSync23 } from "node:fs";
|
|
17252
|
+
import { join as join11, relative as relative5, sep } from "node:path";
|
|
17253
|
+
import { homedir as homedir7 } from "node:os";
|
|
17254
|
+
import { resolve as resolve20 } from "node:path";
|
|
17255
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
16615
17256
|
import { z as z6 } from "zod";
|
|
16616
17257
|
function containsHiddenDirs(packageDir) {
|
|
16617
17258
|
for (const hidden of EXCLUDED_DIR_NAMES) {
|
|
16618
|
-
if (
|
|
17259
|
+
if (existsSync23(`${packageDir}/${hidden}`)) {
|
|
16619
17260
|
return hidden;
|
|
16620
17261
|
}
|
|
16621
17262
|
}
|
|
@@ -16627,12 +17268,12 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
16627
17268
|
function walk(currentDir) {
|
|
16628
17269
|
let entries;
|
|
16629
17270
|
try {
|
|
16630
|
-
entries =
|
|
17271
|
+
entries = readdirSync13(currentDir);
|
|
16631
17272
|
} catch {
|
|
16632
17273
|
return;
|
|
16633
17274
|
}
|
|
16634
17275
|
for (const entry of entries.sort()) {
|
|
16635
|
-
const absPath =
|
|
17276
|
+
const absPath = join11(currentDir, entry);
|
|
16636
17277
|
let lst;
|
|
16637
17278
|
try {
|
|
16638
17279
|
lst = lstatSync6(absPath);
|
|
@@ -16659,9 +17300,9 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
16659
17300
|
}
|
|
16660
17301
|
walk(dir);
|
|
16661
17302
|
files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
|
|
16662
|
-
const top =
|
|
17303
|
+
const top = createHash8("sha256");
|
|
16663
17304
|
for (const f2 of files) {
|
|
16664
|
-
const fileHash =
|
|
17305
|
+
const fileHash = createHash8("sha256").update(readFileSync22(f2.absPath)).digest("hex");
|
|
16665
17306
|
top.update(f2.relativePath, "utf-8");
|
|
16666
17307
|
top.update("\0", "utf-8");
|
|
16667
17308
|
top.update(fileHash, "utf-8");
|
|
@@ -16670,10 +17311,10 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
16670
17311
|
return top.digest("hex");
|
|
16671
17312
|
}
|
|
16672
17313
|
function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
|
|
16673
|
-
if (!
|
|
17314
|
+
if (!existsSync23(path)) return {};
|
|
16674
17315
|
let raw;
|
|
16675
17316
|
try {
|
|
16676
|
-
raw = JSON.parse(
|
|
17317
|
+
raw = JSON.parse(readFileSync22(path, "utf-8"));
|
|
16677
17318
|
} catch {
|
|
16678
17319
|
return {};
|
|
16679
17320
|
}
|
|
@@ -16737,7 +17378,7 @@ var init_install_tracking = __esm({
|
|
|
16737
17378
|
"src/security/install-tracking.ts"() {
|
|
16738
17379
|
"use strict";
|
|
16739
17380
|
init_atomic_write();
|
|
16740
|
-
INSTALLED_MANIFEST_PATH =
|
|
17381
|
+
INSTALLED_MANIFEST_PATH = resolve20(homedir7(), ".massu", "adapter-manifest-installed.json");
|
|
16741
17382
|
DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
16742
17383
|
EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
|
|
16743
17384
|
InstallEntrySchema = z6.object({
|
|
@@ -16760,25 +17401,25 @@ var init_install_tracking = __esm({
|
|
|
16760
17401
|
});
|
|
16761
17402
|
|
|
16762
17403
|
// src/detect/adapters/discover.ts
|
|
16763
|
-
import { existsSync as
|
|
16764
|
-
import { resolve as
|
|
17404
|
+
import { existsSync as existsSync24, readdirSync as readdirSync14, readFileSync as readFileSync23, lstatSync as lstatSync7 } from "node:fs";
|
|
17405
|
+
import { resolve as resolve21, isAbsolute as isAbsolute2 } from "node:path";
|
|
16765
17406
|
import { z as z7 } from "zod";
|
|
16766
17407
|
function walkNodeModules(projectRoot, warnings) {
|
|
16767
|
-
const nodeModulesDir =
|
|
16768
|
-
if (!
|
|
17408
|
+
const nodeModulesDir = resolve21(projectRoot, "node_modules");
|
|
17409
|
+
if (!existsSync24(nodeModulesDir)) {
|
|
16769
17410
|
return [];
|
|
16770
17411
|
}
|
|
16771
17412
|
const candidates = [];
|
|
16772
17413
|
let topLevelEntries;
|
|
16773
17414
|
try {
|
|
16774
|
-
topLevelEntries =
|
|
17415
|
+
topLevelEntries = readdirSync14(nodeModulesDir);
|
|
16775
17416
|
} catch (err) {
|
|
16776
17417
|
warnings.push(`failed to read node_modules: ${err instanceof Error ? err.message : String(err)}`);
|
|
16777
17418
|
return [];
|
|
16778
17419
|
}
|
|
16779
17420
|
for (const entry of topLevelEntries) {
|
|
16780
17421
|
if (entry.startsWith(".")) continue;
|
|
16781
|
-
const entryPath =
|
|
17422
|
+
const entryPath = resolve21(nodeModulesDir, entry);
|
|
16782
17423
|
let entryStat;
|
|
16783
17424
|
try {
|
|
16784
17425
|
entryStat = lstatSync7(entryPath);
|
|
@@ -16795,12 +17436,12 @@ function walkNodeModules(projectRoot, warnings) {
|
|
|
16795
17436
|
if (entry.startsWith("@")) {
|
|
16796
17437
|
let scopedEntries;
|
|
16797
17438
|
try {
|
|
16798
|
-
scopedEntries =
|
|
17439
|
+
scopedEntries = readdirSync14(entryPath);
|
|
16799
17440
|
} catch {
|
|
16800
17441
|
continue;
|
|
16801
17442
|
}
|
|
16802
17443
|
for (const sub of scopedEntries) {
|
|
16803
|
-
const subPath =
|
|
17444
|
+
const subPath = resolve21(entryPath, sub);
|
|
16804
17445
|
let subStat;
|
|
16805
17446
|
try {
|
|
16806
17447
|
subStat = lstatSync7(subPath);
|
|
@@ -16819,11 +17460,11 @@ function walkNodeModules(projectRoot, warnings) {
|
|
|
16819
17460
|
return candidates;
|
|
16820
17461
|
}
|
|
16821
17462
|
function tryReadAdapterPackage(packageDir, warnings) {
|
|
16822
|
-
const pkgJsonPath =
|
|
16823
|
-
if (!
|
|
17463
|
+
const pkgJsonPath = resolve21(packageDir, "package.json");
|
|
17464
|
+
if (!existsSync24(pkgJsonPath)) return null;
|
|
16824
17465
|
let raw;
|
|
16825
17466
|
try {
|
|
16826
|
-
raw = JSON.parse(
|
|
17467
|
+
raw = JSON.parse(readFileSync23(pkgJsonPath, "utf-8"));
|
|
16827
17468
|
} catch (err) {
|
|
16828
17469
|
warnings.push(
|
|
16829
17470
|
`skipping ${packageDir}: package.json parse failed (${err instanceof Error ? err.message : String(err)})`
|
|
@@ -16957,8 +17598,8 @@ function discoverAdapters(opts) {
|
|
|
16957
17598
|
const localSet = new Set(opts.configLocalPaths);
|
|
16958
17599
|
for (const localPath of opts.configLocalPaths) {
|
|
16959
17600
|
if (seenIds.has(localPath)) continue;
|
|
16960
|
-
const absPath = isAbsolute2(localPath) ? localPath :
|
|
16961
|
-
if (!
|
|
17601
|
+
const absPath = isAbsolute2(localPath) ? localPath : resolve21(opts.projectRoot, localPath);
|
|
17602
|
+
if (!existsSync24(absPath)) {
|
|
16962
17603
|
warnings.push(
|
|
16963
17604
|
`local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
|
|
16964
17605
|
);
|
|
@@ -17039,8 +17680,8 @@ __export(adapters_exports, {
|
|
|
17039
17680
|
runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
|
|
17040
17681
|
runAdaptersSearch: () => runAdaptersSearch
|
|
17041
17682
|
});
|
|
17042
|
-
import { existsSync as
|
|
17043
|
-
import { resolve as
|
|
17683
|
+
import { existsSync as existsSync25, readFileSync as readFileSync24 } from "node:fs";
|
|
17684
|
+
import { resolve as resolve22 } from "node:path";
|
|
17044
17685
|
import { parseDocument } from "yaml";
|
|
17045
17686
|
async function handleAdaptersSubcommand(args2) {
|
|
17046
17687
|
const sub = args2[0];
|
|
@@ -17307,8 +17948,8 @@ function mutateLocalArray(mutator, command) {
|
|
|
17307
17948
|
);
|
|
17308
17949
|
return { exitCode: 2 };
|
|
17309
17950
|
}
|
|
17310
|
-
const yamlPath =
|
|
17311
|
-
if (!
|
|
17951
|
+
const yamlPath = resolve22(projectRoot, "massu.config.yaml");
|
|
17952
|
+
if (!existsSync25(yamlPath)) {
|
|
17312
17953
|
process.stderr.write(
|
|
17313
17954
|
`${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
|
|
17314
17955
|
`
|
|
@@ -17317,7 +17958,7 @@ function mutateLocalArray(mutator, command) {
|
|
|
17317
17958
|
}
|
|
17318
17959
|
let yamlText;
|
|
17319
17960
|
try {
|
|
17320
|
-
yamlText =
|
|
17961
|
+
yamlText = readFileSync24(yamlPath, "utf-8");
|
|
17321
17962
|
} catch (err) {
|
|
17322
17963
|
process.stderr.write(`${command}: failed to read ${yamlPath}: ${err instanceof Error ? err.message : String(err)}
|
|
17323
17964
|
`);
|
|
@@ -17338,7 +17979,7 @@ function mutateLocalArray(mutator, command) {
|
|
|
17338
17979
|
if (next === null) {
|
|
17339
17980
|
return { exitCode: 0 };
|
|
17340
17981
|
}
|
|
17341
|
-
const lockPath =
|
|
17982
|
+
const lockPath = resolve22(projectRoot, ".massu", "adapters-local-mutate.lock");
|
|
17342
17983
|
return withFileLockSync(lockPath, () => {
|
|
17343
17984
|
doc.setIn(["adapters", "local"], next);
|
|
17344
17985
|
const newYaml = doc.toString();
|
|
@@ -17407,23 +18048,23 @@ async function runAdaptersInstall(args2) {
|
|
|
17407
18048
|
`);
|
|
17408
18049
|
return { exitCode: 1 };
|
|
17409
18050
|
}
|
|
17410
|
-
const packageDir =
|
|
17411
|
-
if (!
|
|
18051
|
+
const packageDir = resolve22(projectRoot, "node_modules", ...packageName.split("/"));
|
|
18052
|
+
if (!existsSync25(packageDir)) {
|
|
17412
18053
|
process.stderr.write(
|
|
17413
18054
|
`install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
|
|
17414
18055
|
`
|
|
17415
18056
|
);
|
|
17416
18057
|
return { exitCode: 1 };
|
|
17417
18058
|
}
|
|
17418
|
-
const pkgJsonPath =
|
|
17419
|
-
if (!
|
|
18059
|
+
const pkgJsonPath = resolve22(packageDir, "package.json");
|
|
18060
|
+
if (!existsSync25(pkgJsonPath)) {
|
|
17420
18061
|
process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
|
|
17421
18062
|
`);
|
|
17422
18063
|
return { exitCode: 1 };
|
|
17423
18064
|
}
|
|
17424
18065
|
let pkgJson;
|
|
17425
18066
|
try {
|
|
17426
|
-
pkgJson = JSON.parse(
|
|
18067
|
+
pkgJson = JSON.parse(readFileSync24(pkgJsonPath, "utf-8"));
|
|
17427
18068
|
} catch (err) {
|
|
17428
18069
|
process.stderr.write(`install: ${packageName} has malformed package.json: ${err instanceof Error ? err.message : String(err)}
|
|
17429
18070
|
`);
|
|
@@ -17542,8 +18183,8 @@ async function runAdaptersResign(_args) {
|
|
|
17542
18183
|
removeInstalledManifestEntry(name);
|
|
17543
18184
|
continue;
|
|
17544
18185
|
}
|
|
17545
|
-
const packageDir =
|
|
17546
|
-
if (!
|
|
18186
|
+
const packageDir = resolve22(projectRoot, "node_modules", ...name.split("/"));
|
|
18187
|
+
if (!existsSync25(packageDir)) {
|
|
17547
18188
|
removed++;
|
|
17548
18189
|
warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
|
|
17549
18190
|
removeInstalledManifestEntry(name);
|
|
@@ -17630,11 +18271,11 @@ var init_adapters2 = __esm({
|
|
|
17630
18271
|
|
|
17631
18272
|
// src/db.ts
|
|
17632
18273
|
import Database2 from "better-sqlite3";
|
|
17633
|
-
import { dirname as
|
|
17634
|
-
import { existsSync as
|
|
18274
|
+
import { dirname as dirname12, join as join12 } from "path";
|
|
18275
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync10, readdirSync as readdirSync15, statSync as statSync11 } from "fs";
|
|
17635
18276
|
function getCodeGraphDb() {
|
|
17636
18277
|
const dbPath = getResolvedPaths().codegraphDbPath;
|
|
17637
|
-
if (!
|
|
18278
|
+
if (!existsSync26(dbPath)) {
|
|
17638
18279
|
throw new CodegraphDbNotInitializedError(dbPath);
|
|
17639
18280
|
}
|
|
17640
18281
|
const db = new Database2(dbPath, { readonly: true });
|
|
@@ -17643,9 +18284,9 @@ function getCodeGraphDb() {
|
|
|
17643
18284
|
}
|
|
17644
18285
|
function getDataDb() {
|
|
17645
18286
|
const dbPath = getResolvedPaths().dataDbPath;
|
|
17646
|
-
const dir =
|
|
17647
|
-
if (!
|
|
17648
|
-
|
|
18287
|
+
const dir = dirname12(dbPath);
|
|
18288
|
+
if (!existsSync26(dir)) {
|
|
18289
|
+
mkdirSync10(dir, { recursive: true });
|
|
17649
18290
|
}
|
|
17650
18291
|
const db = new Database2(dbPath);
|
|
17651
18292
|
db.pragma("journal_mode = WAL");
|
|
@@ -17915,9 +18556,9 @@ function isPythonDataStale(dataDb, pythonRoot) {
|
|
|
17915
18556
|
const lastBuildTime = new Date(lastBuild.value).getTime();
|
|
17916
18557
|
function checkDir(dir) {
|
|
17917
18558
|
try {
|
|
17918
|
-
const entries =
|
|
18559
|
+
const entries = readdirSync15(dir, { withFileTypes: true });
|
|
17919
18560
|
for (const entry of entries) {
|
|
17920
|
-
const fullPath =
|
|
18561
|
+
const fullPath = join12(dir, entry.name);
|
|
17921
18562
|
if (entry.isDirectory()) {
|
|
17922
18563
|
if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
|
|
17923
18564
|
if (checkDir(fullPath)) return true;
|
|
@@ -17951,10 +18592,10 @@ var init_db = __esm({
|
|
|
17951
18592
|
});
|
|
17952
18593
|
|
|
17953
18594
|
// src/security-utils.ts
|
|
17954
|
-
import { resolve as
|
|
18595
|
+
import { resolve as resolve23, normalize } from "path";
|
|
17955
18596
|
function ensureWithinRoot(filePath, projectRoot) {
|
|
17956
|
-
const resolvedRoot =
|
|
17957
|
-
const resolvedPath =
|
|
18597
|
+
const resolvedRoot = resolve23(projectRoot);
|
|
18598
|
+
const resolvedPath = resolve23(resolvedRoot, filePath);
|
|
17958
18599
|
const normalizedPath = normalize(resolvedPath);
|
|
17959
18600
|
const normalizedRoot = normalize(resolvedRoot);
|
|
17960
18601
|
if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
|
|
@@ -18027,8 +18668,8 @@ var init_rules = __esm({
|
|
|
18027
18668
|
});
|
|
18028
18669
|
|
|
18029
18670
|
// src/import-resolver.ts
|
|
18030
|
-
import { readFileSync as
|
|
18031
|
-
import { resolve as
|
|
18671
|
+
import { readFileSync as readFileSync25, existsSync as existsSync27, statSync as statSync12 } from "fs";
|
|
18672
|
+
import { resolve as resolve24, dirname as dirname13, join as join13 } from "path";
|
|
18032
18673
|
function parseImports(source) {
|
|
18033
18674
|
const imports = [];
|
|
18034
18675
|
const lines = source.split("\n");
|
|
@@ -18084,23 +18725,23 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
18084
18725
|
let basePath;
|
|
18085
18726
|
if (specifier.startsWith("@/")) {
|
|
18086
18727
|
const paths = getResolvedPaths();
|
|
18087
|
-
basePath =
|
|
18728
|
+
basePath = resolve24(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
|
|
18088
18729
|
} else {
|
|
18089
|
-
basePath =
|
|
18730
|
+
basePath = resolve24(dirname13(fromFile), specifier);
|
|
18090
18731
|
}
|
|
18091
|
-
if (
|
|
18732
|
+
if (existsSync27(basePath) && !isDirectory(basePath)) {
|
|
18092
18733
|
return toRelative(basePath);
|
|
18093
18734
|
}
|
|
18094
18735
|
const resolvedPaths = getResolvedPaths();
|
|
18095
18736
|
for (const ext of resolvedPaths.extensions) {
|
|
18096
18737
|
const withExt = basePath + ext;
|
|
18097
|
-
if (
|
|
18738
|
+
if (existsSync27(withExt)) {
|
|
18098
18739
|
return toRelative(withExt);
|
|
18099
18740
|
}
|
|
18100
18741
|
}
|
|
18101
18742
|
for (const indexFile of resolvedPaths.indexFiles) {
|
|
18102
|
-
const indexPath =
|
|
18103
|
-
if (
|
|
18743
|
+
const indexPath = join13(basePath, indexFile);
|
|
18744
|
+
if (existsSync27(indexPath)) {
|
|
18104
18745
|
return toRelative(indexPath);
|
|
18105
18746
|
}
|
|
18106
18747
|
}
|
|
@@ -18136,11 +18777,11 @@ function buildImportIndex(dataDb, codegraphDb) {
|
|
|
18136
18777
|
const batchSize = 500;
|
|
18137
18778
|
let batch = [];
|
|
18138
18779
|
for (const file of files) {
|
|
18139
|
-
const absPath = ensureWithinRoot(
|
|
18140
|
-
if (!
|
|
18780
|
+
const absPath = ensureWithinRoot(resolve24(projectRoot, file.path), projectRoot);
|
|
18781
|
+
if (!existsSync27(absPath)) continue;
|
|
18141
18782
|
let source;
|
|
18142
18783
|
try {
|
|
18143
|
-
source =
|
|
18784
|
+
source = readFileSync25(absPath, "utf-8");
|
|
18144
18785
|
} catch {
|
|
18145
18786
|
continue;
|
|
18146
18787
|
}
|
|
@@ -18176,15 +18817,15 @@ var init_import_resolver = __esm({
|
|
|
18176
18817
|
});
|
|
18177
18818
|
|
|
18178
18819
|
// src/trpc-index.ts
|
|
18179
|
-
import { readFileSync as
|
|
18180
|
-
import { resolve as
|
|
18820
|
+
import { readFileSync as readFileSync26, existsSync as existsSync28, readdirSync as readdirSync16 } from "fs";
|
|
18821
|
+
import { resolve as resolve25, join as join14 } from "path";
|
|
18181
18822
|
function parseRootRouter() {
|
|
18182
18823
|
const paths = getResolvedPaths();
|
|
18183
18824
|
const rootPath = paths.rootRouterPath;
|
|
18184
|
-
if (!
|
|
18825
|
+
if (!existsSync28(rootPath)) {
|
|
18185
18826
|
throw new Error(`Root router not found at ${rootPath}`);
|
|
18186
18827
|
}
|
|
18187
|
-
const source =
|
|
18828
|
+
const source = readFileSync26(rootPath, "utf-8");
|
|
18188
18829
|
const mappings = [];
|
|
18189
18830
|
const importMap = /* @__PURE__ */ new Map();
|
|
18190
18831
|
const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
|
|
@@ -18192,16 +18833,16 @@ function parseRootRouter() {
|
|
|
18192
18833
|
while ((match = importRegex.exec(source)) !== null) {
|
|
18193
18834
|
const variable = match[1];
|
|
18194
18835
|
let filePath = match[2];
|
|
18195
|
-
const fullPath =
|
|
18836
|
+
const fullPath = resolve25(paths.routersDir, filePath);
|
|
18196
18837
|
for (const ext of [".ts", ".tsx", ""]) {
|
|
18197
18838
|
const candidate = fullPath + ext;
|
|
18198
18839
|
const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
|
|
18199
|
-
if (
|
|
18840
|
+
if (existsSync28(candidate)) {
|
|
18200
18841
|
filePath = routersRelPath + "/" + filePath + ext;
|
|
18201
18842
|
break;
|
|
18202
18843
|
}
|
|
18203
|
-
const indexCandidate =
|
|
18204
|
-
if (
|
|
18844
|
+
const indexCandidate = join14(fullPath, "index.ts");
|
|
18845
|
+
if (existsSync28(indexCandidate)) {
|
|
18205
18846
|
filePath = routersRelPath + "/" + filePath + "/index.ts";
|
|
18206
18847
|
break;
|
|
18207
18848
|
}
|
|
@@ -18220,9 +18861,9 @@ function parseRootRouter() {
|
|
|
18220
18861
|
return mappings;
|
|
18221
18862
|
}
|
|
18222
18863
|
function extractProcedures(routerFilePath) {
|
|
18223
|
-
const absPath =
|
|
18224
|
-
if (!
|
|
18225
|
-
const source =
|
|
18864
|
+
const absPath = resolve25(getProjectRoot(), routerFilePath);
|
|
18865
|
+
if (!existsSync28(absPath)) return [];
|
|
18866
|
+
const source = readFileSync26(absPath, "utf-8");
|
|
18226
18867
|
const procedures = [];
|
|
18227
18868
|
const seen = /* @__PURE__ */ new Set();
|
|
18228
18869
|
const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
|
|
@@ -18245,27 +18886,27 @@ function findUICallSites(routerKey, procedureName) {
|
|
|
18245
18886
|
const root = getProjectRoot();
|
|
18246
18887
|
const src = config.paths.source;
|
|
18247
18888
|
const searchDirs = [
|
|
18248
|
-
|
|
18249
|
-
|
|
18250
|
-
|
|
18889
|
+
resolve25(root, config.paths.pages ?? src + "/app"),
|
|
18890
|
+
resolve25(root, config.paths.components ?? src + "/components"),
|
|
18891
|
+
resolve25(root, config.paths.hooks ?? src + "/hooks")
|
|
18251
18892
|
];
|
|
18252
18893
|
const searchPattern = `api.${routerKey}.${procedureName}`;
|
|
18253
18894
|
for (const dir of searchDirs) {
|
|
18254
|
-
if (!
|
|
18895
|
+
if (!existsSync28(dir)) continue;
|
|
18255
18896
|
searchDirectory(dir, searchPattern, callSites);
|
|
18256
18897
|
}
|
|
18257
18898
|
return callSites;
|
|
18258
18899
|
}
|
|
18259
18900
|
function searchDirectory(dir, pattern, results) {
|
|
18260
|
-
const entries =
|
|
18901
|
+
const entries = readdirSync16(dir, { withFileTypes: true });
|
|
18261
18902
|
for (const entry of entries) {
|
|
18262
|
-
const fullPath =
|
|
18903
|
+
const fullPath = join14(dir, entry.name);
|
|
18263
18904
|
if (entry.isDirectory()) {
|
|
18264
18905
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
18265
18906
|
searchDirectory(fullPath, pattern, results);
|
|
18266
18907
|
} else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
|
|
18267
18908
|
try {
|
|
18268
|
-
const source =
|
|
18909
|
+
const source = readFileSync26(fullPath, "utf-8");
|
|
18269
18910
|
const lines = source.split("\n");
|
|
18270
18911
|
for (let i = 0; i < lines.length; i++) {
|
|
18271
18912
|
if (lines[i].includes(pattern)) {
|
|
@@ -18328,8 +18969,8 @@ var init_trpc_index = __esm({
|
|
|
18328
18969
|
});
|
|
18329
18970
|
|
|
18330
18971
|
// src/page-deps.ts
|
|
18331
|
-
import { readFileSync as
|
|
18332
|
-
import { resolve as
|
|
18972
|
+
import { readFileSync as readFileSync27, existsSync as existsSync29 } from "fs";
|
|
18973
|
+
import { resolve as resolve26 } from "path";
|
|
18333
18974
|
function deriveRoute(pageFile) {
|
|
18334
18975
|
let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
|
|
18335
18976
|
return route || "/";
|
|
@@ -18367,10 +19008,10 @@ function findRouterCalls(files) {
|
|
|
18367
19008
|
const routers = /* @__PURE__ */ new Set();
|
|
18368
19009
|
const projectRoot = getProjectRoot();
|
|
18369
19010
|
for (const file of files) {
|
|
18370
|
-
const absPath = ensureWithinRoot(
|
|
18371
|
-
if (!
|
|
19011
|
+
const absPath = ensureWithinRoot(resolve26(projectRoot, file), projectRoot);
|
|
19012
|
+
if (!existsSync29(absPath)) continue;
|
|
18372
19013
|
try {
|
|
18373
|
-
const source =
|
|
19014
|
+
const source = readFileSync27(absPath, "utf-8");
|
|
18374
19015
|
const apiCallRegex = /api\.(\w+)\.\w+/g;
|
|
18375
19016
|
let match;
|
|
18376
19017
|
while ((match = apiCallRegex.exec(source)) !== null) {
|
|
@@ -18388,10 +19029,10 @@ function findTablesFromRouters(routerNames, dataDb) {
|
|
|
18388
19029
|
"SELECT DISTINCT router_file FROM massu_trpc_procedures WHERE router_name = ?"
|
|
18389
19030
|
).all(routerName);
|
|
18390
19031
|
for (const proc of procs) {
|
|
18391
|
-
const absPath = ensureWithinRoot(
|
|
18392
|
-
if (!
|
|
19032
|
+
const absPath = ensureWithinRoot(resolve26(getProjectRoot(), proc.router_file), getProjectRoot());
|
|
19033
|
+
if (!existsSync29(absPath)) continue;
|
|
18393
19034
|
try {
|
|
18394
|
-
const source =
|
|
19035
|
+
const source = readFileSync27(absPath, "utf-8");
|
|
18395
19036
|
const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
|
|
18396
19037
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
18397
19038
|
const tableRegex = new RegExp(regexStr + "\\.", "g");
|
|
@@ -18660,14 +19301,14 @@ var init_domains = __esm({
|
|
|
18660
19301
|
});
|
|
18661
19302
|
|
|
18662
19303
|
// src/schema-mapper.ts
|
|
18663
|
-
import { readFileSync as
|
|
18664
|
-
import { join as
|
|
19304
|
+
import { readFileSync as readFileSync28, existsSync as existsSync30, readdirSync as readdirSync17 } from "fs";
|
|
19305
|
+
import { join as join15 } from "path";
|
|
18665
19306
|
function parsePrismaSchema() {
|
|
18666
19307
|
const schemaPath = getResolvedPaths().prismaSchemaPath;
|
|
18667
|
-
if (!
|
|
19308
|
+
if (!existsSync30(schemaPath)) {
|
|
18668
19309
|
throw new Error(`Prisma schema not found at ${schemaPath}`);
|
|
18669
19310
|
}
|
|
18670
|
-
const source =
|
|
19311
|
+
const source = readFileSync28(schemaPath, "utf-8");
|
|
18671
19312
|
const models = [];
|
|
18672
19313
|
const sourceLines = source.split("\n");
|
|
18673
19314
|
let i = 0;
|
|
@@ -18725,14 +19366,14 @@ function toSnakeCase(str) {
|
|
|
18725
19366
|
function findColumnUsageInRouters(tableName) {
|
|
18726
19367
|
const usage = /* @__PURE__ */ new Map();
|
|
18727
19368
|
const routersDir = getResolvedPaths().routersDir;
|
|
18728
|
-
if (!
|
|
19369
|
+
if (!existsSync30(routersDir)) return usage;
|
|
18729
19370
|
scanDirectory(routersDir, tableName, usage);
|
|
18730
19371
|
return usage;
|
|
18731
19372
|
}
|
|
18732
19373
|
function scanDirectory(dir, tableName, usage) {
|
|
18733
|
-
const entries =
|
|
19374
|
+
const entries = readdirSync17(dir, { withFileTypes: true });
|
|
18734
19375
|
for (const entry of entries) {
|
|
18735
|
-
const fullPath =
|
|
19376
|
+
const fullPath = join15(dir, entry.name);
|
|
18736
19377
|
if (entry.isDirectory()) {
|
|
18737
19378
|
scanDirectory(fullPath, tableName, usage);
|
|
18738
19379
|
} else if (entry.name.endsWith(".ts")) {
|
|
@@ -18742,7 +19383,7 @@ function scanDirectory(dir, tableName, usage) {
|
|
|
18742
19383
|
}
|
|
18743
19384
|
function scanFile(absPath, tableName, usage) {
|
|
18744
19385
|
try {
|
|
18745
|
-
const source =
|
|
19386
|
+
const source = readFileSync28(absPath, "utf-8");
|
|
18746
19387
|
if (!source.includes(tableName)) return;
|
|
18747
19388
|
const relPath = absPath.slice(getProjectRoot().length + 1);
|
|
18748
19389
|
const lines = source.split("\n");
|
|
@@ -18787,15 +19428,15 @@ function detectMismatches(models) {
|
|
|
18787
19428
|
}
|
|
18788
19429
|
function findFilesUsingColumn(dir, column, tableName) {
|
|
18789
19430
|
const result = [];
|
|
18790
|
-
if (!
|
|
18791
|
-
const entries =
|
|
19431
|
+
if (!existsSync30(dir)) return result;
|
|
19432
|
+
const entries = readdirSync17(dir, { withFileTypes: true });
|
|
18792
19433
|
for (const entry of entries) {
|
|
18793
|
-
const fullPath =
|
|
19434
|
+
const fullPath = join15(dir, entry.name);
|
|
18794
19435
|
if (entry.isDirectory()) {
|
|
18795
19436
|
result.push(...findFilesUsingColumn(fullPath, column, tableName));
|
|
18796
19437
|
} else if (entry.name.endsWith(".ts")) {
|
|
18797
19438
|
try {
|
|
18798
|
-
const source =
|
|
19439
|
+
const source = readFileSync28(fullPath, "utf-8");
|
|
18799
19440
|
if (source.includes(tableName) && source.includes(column)) {
|
|
18800
19441
|
result.push(fullPath.slice(getProjectRoot().length + 1));
|
|
18801
19442
|
}
|
|
@@ -18940,34 +19581,34 @@ var init_import_parser = __esm({
|
|
|
18940
19581
|
});
|
|
18941
19582
|
|
|
18942
19583
|
// src/python/import-resolver.ts
|
|
18943
|
-
import { readFileSync as
|
|
18944
|
-
import { resolve as
|
|
19584
|
+
import { readFileSync as readFileSync29, existsSync as existsSync31, readdirSync as readdirSync18 } from "fs";
|
|
19585
|
+
import { resolve as resolve28, join as join16, relative as relative6, dirname as dirname14 } from "path";
|
|
18945
19586
|
function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
|
|
18946
19587
|
const projectRoot = getProjectRoot();
|
|
18947
19588
|
if (level > 0) {
|
|
18948
|
-
let baseDir =
|
|
19589
|
+
let baseDir = dirname14(resolve28(projectRoot, fromFile));
|
|
18949
19590
|
for (let i = 1; i < level; i++) {
|
|
18950
|
-
baseDir =
|
|
19591
|
+
baseDir = dirname14(baseDir);
|
|
18951
19592
|
}
|
|
18952
19593
|
const modulePart = module.replace(/^\.+/, "");
|
|
18953
19594
|
if (modulePart) {
|
|
18954
19595
|
const parts2 = modulePart.split(".");
|
|
18955
|
-
return tryResolvePythonPath(
|
|
19596
|
+
return tryResolvePythonPath(join16(baseDir, ...parts2), projectRoot);
|
|
18956
19597
|
}
|
|
18957
19598
|
return tryResolvePythonPath(baseDir, projectRoot);
|
|
18958
19599
|
}
|
|
18959
19600
|
const parts = module.split(".");
|
|
18960
|
-
const candidate =
|
|
19601
|
+
const candidate = join16(resolve28(projectRoot, pythonRoot), ...parts);
|
|
18961
19602
|
return tryResolvePythonPath(candidate, projectRoot);
|
|
18962
19603
|
}
|
|
18963
19604
|
function tryResolvePythonPath(basePath, projectRoot) {
|
|
18964
|
-
if (
|
|
19605
|
+
if (existsSync31(basePath + ".py")) {
|
|
18965
19606
|
return relative6(projectRoot, basePath + ".py");
|
|
18966
19607
|
}
|
|
18967
|
-
if (
|
|
18968
|
-
return relative6(projectRoot,
|
|
19608
|
+
if (existsSync31(join16(basePath, "__init__.py"))) {
|
|
19609
|
+
return relative6(projectRoot, join16(basePath, "__init__.py"));
|
|
18969
19610
|
}
|
|
18970
|
-
if (basePath.endsWith(".py") &&
|
|
19611
|
+
if (basePath.endsWith(".py") && existsSync31(basePath)) {
|
|
18971
19612
|
return relative6(projectRoot, basePath);
|
|
18972
19613
|
}
|
|
18973
19614
|
return null;
|
|
@@ -18975,13 +19616,13 @@ function tryResolvePythonPath(basePath, projectRoot) {
|
|
|
18975
19616
|
function walkPythonFiles(dir, excludeDirs) {
|
|
18976
19617
|
const files = [];
|
|
18977
19618
|
try {
|
|
18978
|
-
const entries =
|
|
19619
|
+
const entries = readdirSync18(dir, { withFileTypes: true });
|
|
18979
19620
|
for (const entry of entries) {
|
|
18980
19621
|
if (entry.isDirectory()) {
|
|
18981
19622
|
if (excludeDirs.includes(entry.name)) continue;
|
|
18982
|
-
files.push(...walkPythonFiles(
|
|
19623
|
+
files.push(...walkPythonFiles(join16(dir, entry.name), excludeDirs));
|
|
18983
19624
|
} else if (entry.name.endsWith(".py")) {
|
|
18984
|
-
files.push(
|
|
19625
|
+
files.push(join16(dir, entry.name));
|
|
18985
19626
|
}
|
|
18986
19627
|
}
|
|
18987
19628
|
} catch {
|
|
@@ -18990,7 +19631,7 @@ function walkPythonFiles(dir, excludeDirs) {
|
|
|
18990
19631
|
}
|
|
18991
19632
|
function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
18992
19633
|
const projectRoot = getProjectRoot();
|
|
18993
|
-
const absRoot =
|
|
19634
|
+
const absRoot = resolve28(projectRoot, pythonRoot);
|
|
18994
19635
|
dataDb.exec("DELETE FROM massu_py_imports");
|
|
18995
19636
|
const insertStmt = dataDb.prepare(
|
|
18996
19637
|
"INSERT INTO massu_py_imports (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)"
|
|
@@ -19007,7 +19648,7 @@ function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__"
|
|
|
19007
19648
|
const relFile = relative6(projectRoot, absFile);
|
|
19008
19649
|
let source;
|
|
19009
19650
|
try {
|
|
19010
|
-
source =
|
|
19651
|
+
source = readFileSync29(absFile, "utf-8");
|
|
19011
19652
|
} catch {
|
|
19012
19653
|
continue;
|
|
19013
19654
|
}
|
|
@@ -19273,18 +19914,18 @@ var init_route_parser = __esm({
|
|
|
19273
19914
|
});
|
|
19274
19915
|
|
|
19275
19916
|
// src/python/route-indexer.ts
|
|
19276
|
-
import { readFileSync as
|
|
19277
|
-
import { join as
|
|
19917
|
+
import { readFileSync as readFileSync30, readdirSync as readdirSync19 } from "fs";
|
|
19918
|
+
import { join as join17, relative as relative7 } from "path";
|
|
19278
19919
|
function walkPyFiles(dir, excludeDirs) {
|
|
19279
19920
|
const files = [];
|
|
19280
19921
|
try {
|
|
19281
|
-
const entries =
|
|
19922
|
+
const entries = readdirSync19(dir, { withFileTypes: true });
|
|
19282
19923
|
for (const entry of entries) {
|
|
19283
19924
|
if (entry.isDirectory()) {
|
|
19284
19925
|
if (excludeDirs.includes(entry.name)) continue;
|
|
19285
|
-
files.push(...walkPyFiles(
|
|
19926
|
+
files.push(...walkPyFiles(join17(dir, entry.name), excludeDirs));
|
|
19286
19927
|
} else if (entry.name.endsWith(".py")) {
|
|
19287
|
-
files.push(
|
|
19928
|
+
files.push(join17(dir, entry.name));
|
|
19288
19929
|
}
|
|
19289
19930
|
}
|
|
19290
19931
|
} catch {
|
|
@@ -19293,7 +19934,7 @@ function walkPyFiles(dir, excludeDirs) {
|
|
|
19293
19934
|
}
|
|
19294
19935
|
function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
19295
19936
|
const projectRoot = getProjectRoot();
|
|
19296
|
-
const absRoot =
|
|
19937
|
+
const absRoot = join17(projectRoot, pythonRoot);
|
|
19297
19938
|
dataDb.exec("DELETE FROM massu_py_routes");
|
|
19298
19939
|
dataDb.exec("DELETE FROM massu_py_route_callers");
|
|
19299
19940
|
const insertStmt = dataDb.prepare(
|
|
@@ -19306,7 +19947,7 @@ function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
|
|
|
19306
19947
|
const relFile = relative7(projectRoot, absFile);
|
|
19307
19948
|
let source;
|
|
19308
19949
|
try {
|
|
19309
|
-
source =
|
|
19950
|
+
source = readFileSync30(absFile, "utf-8");
|
|
19310
19951
|
} catch {
|
|
19311
19952
|
continue;
|
|
19312
19953
|
}
|
|
@@ -19517,18 +20158,18 @@ var init_model_parser = __esm({
|
|
|
19517
20158
|
});
|
|
19518
20159
|
|
|
19519
20160
|
// src/python/model-indexer.ts
|
|
19520
|
-
import { readFileSync as
|
|
19521
|
-
import { join as
|
|
20161
|
+
import { readFileSync as readFileSync31, readdirSync as readdirSync20 } from "fs";
|
|
20162
|
+
import { join as join18, relative as relative8 } from "path";
|
|
19522
20163
|
function walkPyFiles2(dir, excludeDirs) {
|
|
19523
20164
|
const files = [];
|
|
19524
20165
|
try {
|
|
19525
|
-
const entries =
|
|
20166
|
+
const entries = readdirSync20(dir, { withFileTypes: true });
|
|
19526
20167
|
for (const entry of entries) {
|
|
19527
20168
|
if (entry.isDirectory()) {
|
|
19528
20169
|
if (excludeDirs.includes(entry.name)) continue;
|
|
19529
|
-
files.push(...walkPyFiles2(
|
|
20170
|
+
files.push(...walkPyFiles2(join18(dir, entry.name), excludeDirs));
|
|
19530
20171
|
} else if (entry.name.endsWith(".py")) {
|
|
19531
|
-
files.push(
|
|
20172
|
+
files.push(join18(dir, entry.name));
|
|
19532
20173
|
}
|
|
19533
20174
|
}
|
|
19534
20175
|
} catch {
|
|
@@ -19537,7 +20178,7 @@ function walkPyFiles2(dir, excludeDirs) {
|
|
|
19537
20178
|
}
|
|
19538
20179
|
function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
19539
20180
|
const projectRoot = getProjectRoot();
|
|
19540
|
-
const absRoot =
|
|
20181
|
+
const absRoot = join18(projectRoot, pythonRoot);
|
|
19541
20182
|
dataDb.exec("DELETE FROM massu_py_models");
|
|
19542
20183
|
dataDb.exec("DELETE FROM massu_py_fk_edges");
|
|
19543
20184
|
const insertModel = dataDb.prepare(
|
|
@@ -19553,7 +20194,7 @@ function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
|
|
|
19553
20194
|
const relFile = relative8(projectRoot, absFile);
|
|
19554
20195
|
let source;
|
|
19555
20196
|
try {
|
|
19556
|
-
source =
|
|
20197
|
+
source = readFileSync31(absFile, "utf-8");
|
|
19557
20198
|
} catch {
|
|
19558
20199
|
continue;
|
|
19559
20200
|
}
|
|
@@ -19813,19 +20454,19 @@ var init_migration_parser = __esm({
|
|
|
19813
20454
|
});
|
|
19814
20455
|
|
|
19815
20456
|
// src/python/migration-indexer.ts
|
|
19816
|
-
import { readFileSync as
|
|
19817
|
-
import { join as
|
|
20457
|
+
import { readFileSync as readFileSync32, readdirSync as readdirSync21 } from "fs";
|
|
20458
|
+
import { join as join19, relative as relative9 } from "path";
|
|
19818
20459
|
function buildPythonMigrationIndex(dataDb, alembicDir) {
|
|
19819
20460
|
const projectRoot = getProjectRoot();
|
|
19820
|
-
const absDir =
|
|
20461
|
+
const absDir = join19(projectRoot, alembicDir);
|
|
19821
20462
|
dataDb.exec("DELETE FROM massu_py_migrations");
|
|
19822
|
-
const versionsDir =
|
|
20463
|
+
const versionsDir = join19(absDir, "versions");
|
|
19823
20464
|
let files = [];
|
|
19824
20465
|
try {
|
|
19825
|
-
files =
|
|
20466
|
+
files = readdirSync21(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join19(versionsDir, f2));
|
|
19826
20467
|
} catch {
|
|
19827
20468
|
try {
|
|
19828
|
-
files =
|
|
20469
|
+
files = readdirSync21(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join19(absDir, f2));
|
|
19829
20470
|
} catch {
|
|
19830
20471
|
}
|
|
19831
20472
|
}
|
|
@@ -19839,7 +20480,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
|
|
|
19839
20480
|
for (const absFile of files) {
|
|
19840
20481
|
let source;
|
|
19841
20482
|
try {
|
|
19842
|
-
source =
|
|
20483
|
+
source = readFileSync32(absFile, "utf-8");
|
|
19843
20484
|
} catch {
|
|
19844
20485
|
continue;
|
|
19845
20486
|
}
|
|
@@ -19873,12 +20514,12 @@ var init_migration_indexer = __esm({
|
|
|
19873
20514
|
});
|
|
19874
20515
|
|
|
19875
20516
|
// src/python/coupling-detector.ts
|
|
19876
|
-
import { readFileSync as
|
|
19877
|
-
import { join as
|
|
20517
|
+
import { readFileSync as readFileSync33, readdirSync as readdirSync22 } from "fs";
|
|
20518
|
+
import { join as join20, relative as relative10 } from "path";
|
|
19878
20519
|
function buildPythonCouplingIndex(dataDb) {
|
|
19879
20520
|
const projectRoot = getProjectRoot();
|
|
19880
20521
|
const config = getConfig();
|
|
19881
|
-
const srcDir =
|
|
20522
|
+
const srcDir = join20(projectRoot, config.paths.source);
|
|
19882
20523
|
const routes = dataDb.prepare("SELECT id, method, path FROM massu_py_routes").all();
|
|
19883
20524
|
if (routes.length === 0) return 0;
|
|
19884
20525
|
dataDb.exec("DELETE FROM massu_py_route_callers");
|
|
@@ -19910,7 +20551,7 @@ function buildPythonCouplingIndex(dataDb) {
|
|
|
19910
20551
|
const relFile = relative10(projectRoot, absFile);
|
|
19911
20552
|
let source;
|
|
19912
20553
|
try {
|
|
19913
|
-
source =
|
|
20554
|
+
source = readFileSync33(absFile, "utf-8");
|
|
19914
20555
|
} catch {
|
|
19915
20556
|
continue;
|
|
19916
20557
|
}
|
|
@@ -19938,13 +20579,13 @@ function walkFrontendFiles(dir) {
|
|
|
19938
20579
|
const files = [];
|
|
19939
20580
|
const exclude = ["node_modules", ".next", "dist", ".git", "__pycache__", ".venv", "venv"];
|
|
19940
20581
|
try {
|
|
19941
|
-
const entries =
|
|
20582
|
+
const entries = readdirSync22(dir, { withFileTypes: true });
|
|
19942
20583
|
for (const entry of entries) {
|
|
19943
20584
|
if (entry.isDirectory()) {
|
|
19944
20585
|
if (exclude.includes(entry.name)) continue;
|
|
19945
|
-
files.push(...walkFrontendFiles(
|
|
20586
|
+
files.push(...walkFrontendFiles(join20(dir, entry.name)));
|
|
19946
20587
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
19947
|
-
files.push(
|
|
20588
|
+
files.push(join20(dir, entry.name));
|
|
19948
20589
|
}
|
|
19949
20590
|
}
|
|
19950
20591
|
} catch {
|
|
@@ -20315,8 +20956,8 @@ var init_memory_tools = __esm({
|
|
|
20315
20956
|
});
|
|
20316
20957
|
|
|
20317
20958
|
// src/docs-tools.ts
|
|
20318
|
-
import { readFileSync as
|
|
20319
|
-
import { resolve as
|
|
20959
|
+
import { readFileSync as readFileSync34, existsSync as existsSync32 } from "fs";
|
|
20960
|
+
import { resolve as resolve29, basename as basename6 } from "path";
|
|
20320
20961
|
function p3(baseName) {
|
|
20321
20962
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
20322
20963
|
}
|
|
@@ -20371,10 +21012,10 @@ function handleDocsToolCall(name, args2) {
|
|
|
20371
21012
|
}
|
|
20372
21013
|
function loadDocsMap() {
|
|
20373
21014
|
const mapPath = getResolvedPaths().docsMapPath;
|
|
20374
|
-
if (!
|
|
21015
|
+
if (!existsSync32(mapPath)) {
|
|
20375
21016
|
throw new Error(`docs-map.json not found at ${mapPath}`);
|
|
20376
21017
|
}
|
|
20377
|
-
return JSON.parse(
|
|
21018
|
+
return JSON.parse(readFileSync34(mapPath, "utf-8"));
|
|
20378
21019
|
}
|
|
20379
21020
|
function matchesPattern(filePath, pattern) {
|
|
20380
21021
|
const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
@@ -20444,13 +21085,13 @@ function extractFrontmatter(content) {
|
|
|
20444
21085
|
}
|
|
20445
21086
|
function extractProcedureNames(routerPath) {
|
|
20446
21087
|
const root = getProjectRoot();
|
|
20447
|
-
const absPath = ensureWithinRoot(
|
|
20448
|
-
if (!
|
|
20449
|
-
const altPath = ensureWithinRoot(
|
|
20450
|
-
if (!
|
|
20451
|
-
return extractProcedureNamesFromContent(
|
|
21088
|
+
const absPath = ensureWithinRoot(resolve29(getResolvedPaths().srcDir, "..", routerPath), root);
|
|
21089
|
+
if (!existsSync32(absPath)) {
|
|
21090
|
+
const altPath = ensureWithinRoot(resolve29(getResolvedPaths().srcDir, "../server/api/routers", basename6(routerPath)), root);
|
|
21091
|
+
if (!existsSync32(altPath)) return [];
|
|
21092
|
+
return extractProcedureNamesFromContent(readFileSync34(altPath, "utf-8"));
|
|
20452
21093
|
}
|
|
20453
|
-
return extractProcedureNamesFromContent(
|
|
21094
|
+
return extractProcedureNamesFromContent(readFileSync34(absPath, "utf-8"));
|
|
20454
21095
|
}
|
|
20455
21096
|
function extractProcedureNamesFromContent(content) {
|
|
20456
21097
|
const procRegex = /\.(?:query|mutation)\s*\(/g;
|
|
@@ -20490,8 +21131,8 @@ function handleDocsAudit(args2) {
|
|
|
20490
21131
|
for (const [mappingId, triggeringFiles] of affectedMappings) {
|
|
20491
21132
|
const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
|
|
20492
21133
|
if (!mapping) continue;
|
|
20493
|
-
const helpPagePath = ensureWithinRoot(
|
|
20494
|
-
if (!
|
|
21134
|
+
const helpPagePath = ensureWithinRoot(resolve29(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
21135
|
+
if (!existsSync32(helpPagePath)) {
|
|
20495
21136
|
results.push({
|
|
20496
21137
|
helpPage: mapping.helpPage,
|
|
20497
21138
|
mappingId,
|
|
@@ -20503,7 +21144,7 @@ function handleDocsAudit(args2) {
|
|
|
20503
21144
|
});
|
|
20504
21145
|
continue;
|
|
20505
21146
|
}
|
|
20506
|
-
const content =
|
|
21147
|
+
const content = readFileSync34(helpPagePath, "utf-8");
|
|
20507
21148
|
const sections = extractSections(content);
|
|
20508
21149
|
const frontmatter = extractFrontmatter(content);
|
|
20509
21150
|
const staleReasons = [];
|
|
@@ -20543,9 +21184,9 @@ function handleDocsAudit(args2) {
|
|
|
20543
21184
|
});
|
|
20544
21185
|
for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
|
|
20545
21186
|
if (parentId === mappingId) {
|
|
20546
|
-
const guidePath = ensureWithinRoot(
|
|
20547
|
-
if (
|
|
20548
|
-
const guideContent =
|
|
21187
|
+
const guidePath = ensureWithinRoot(resolve29(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
|
|
21188
|
+
if (existsSync32(guidePath)) {
|
|
21189
|
+
const guideContent = readFileSync34(guidePath, "utf-8");
|
|
20549
21190
|
const guideFrontmatter = extractFrontmatter(guideContent);
|
|
20550
21191
|
if (!guideFrontmatter?.lastVerified || status === "STALE") {
|
|
20551
21192
|
results.push({
|
|
@@ -20578,14 +21219,14 @@ function handleDocsCoverage(args2) {
|
|
|
20578
21219
|
const gaps = [];
|
|
20579
21220
|
const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
|
|
20580
21221
|
for (const mapping of mappings) {
|
|
20581
|
-
const helpPagePath = ensureWithinRoot(
|
|
20582
|
-
const exists =
|
|
21222
|
+
const helpPagePath = ensureWithinRoot(resolve29(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
21223
|
+
const exists = existsSync32(helpPagePath);
|
|
20583
21224
|
let hasContent = false;
|
|
20584
21225
|
let lineCount = 0;
|
|
20585
21226
|
let lastVerified = null;
|
|
20586
21227
|
let status = null;
|
|
20587
21228
|
if (exists) {
|
|
20588
|
-
const content =
|
|
21229
|
+
const content = readFileSync34(helpPagePath, "utf-8");
|
|
20589
21230
|
lineCount = content.split("\n").length;
|
|
20590
21231
|
hasContent = lineCount > 10;
|
|
20591
21232
|
const frontmatter = extractFrontmatter(content);
|
|
@@ -20926,8 +21567,8 @@ var init_observability_tools = __esm({
|
|
|
20926
21567
|
});
|
|
20927
21568
|
|
|
20928
21569
|
// src/sentinel-db.ts
|
|
20929
|
-
import { existsSync as
|
|
20930
|
-
import { resolve as
|
|
21570
|
+
import { existsSync as existsSync33 } from "fs";
|
|
21571
|
+
import { resolve as resolve30 } from "path";
|
|
20931
21572
|
function parsePortalScope(raw) {
|
|
20932
21573
|
if (!raw) return [];
|
|
20933
21574
|
try {
|
|
@@ -21163,23 +21804,23 @@ function validateFeatures(db, domainFilter) {
|
|
|
21163
21804
|
const missingProcedures = [];
|
|
21164
21805
|
const missingPages = [];
|
|
21165
21806
|
for (const comp of components) {
|
|
21166
|
-
const absPath =
|
|
21167
|
-
if (!
|
|
21807
|
+
const absPath = resolve30(PROJECT_ROOT, comp.component_file);
|
|
21808
|
+
if (!existsSync33(absPath)) {
|
|
21168
21809
|
missingComponents.push(comp.component_file);
|
|
21169
21810
|
}
|
|
21170
21811
|
}
|
|
21171
21812
|
for (const proc of procedures) {
|
|
21172
|
-
const routerPath =
|
|
21173
|
-
if (!
|
|
21813
|
+
const routerPath = resolve30(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
|
|
21814
|
+
if (!existsSync33(routerPath)) {
|
|
21174
21815
|
missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
|
|
21175
21816
|
}
|
|
21176
21817
|
}
|
|
21177
21818
|
for (const page of pages) {
|
|
21178
21819
|
const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
|
|
21179
|
-
const absPath =
|
|
21180
|
-
if (page.page_route.startsWith("/") && !
|
|
21181
|
-
const altPath =
|
|
21182
|
-
if (!
|
|
21820
|
+
const absPath = resolve30(PROJECT_ROOT, routeToPath);
|
|
21821
|
+
if (page.page_route.startsWith("/") && !existsSync33(absPath)) {
|
|
21822
|
+
const altPath = resolve30(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
|
|
21823
|
+
if (!existsSync33(altPath)) {
|
|
21183
21824
|
missingPages.push(page.page_route);
|
|
21184
21825
|
}
|
|
21185
21826
|
}
|
|
@@ -21703,8 +22344,8 @@ var init_sentinel_tools = __esm({
|
|
|
21703
22344
|
});
|
|
21704
22345
|
|
|
21705
22346
|
// src/sentinel-scanner.ts
|
|
21706
|
-
import { readFileSync as
|
|
21707
|
-
import { resolve as
|
|
22347
|
+
import { readFileSync as readFileSync35, existsSync as existsSync34, readdirSync as readdirSync23, statSync as statSync13 } from "fs";
|
|
22348
|
+
import { resolve as resolve31, join as join21, basename as basename7, dirname as dirname15, relative as relative11 } from "path";
|
|
21708
22349
|
function inferDomain(filePath) {
|
|
21709
22350
|
const domains = getConfig().domains;
|
|
21710
22351
|
const path = filePath.toLowerCase();
|
|
@@ -21833,10 +22474,10 @@ function scanComponentExports(dataDb) {
|
|
|
21833
22474
|
const projectRoot = getProjectRoot();
|
|
21834
22475
|
const componentsBase = config.paths.components ?? config.paths.source + "/components";
|
|
21835
22476
|
const componentDirs = [];
|
|
21836
|
-
const basePath =
|
|
21837
|
-
if (
|
|
22477
|
+
const basePath = resolve31(projectRoot, componentsBase);
|
|
22478
|
+
if (existsSync34(basePath)) {
|
|
21838
22479
|
try {
|
|
21839
|
-
const entries =
|
|
22480
|
+
const entries = readdirSync23(basePath, { withFileTypes: true });
|
|
21840
22481
|
for (const entry of entries) {
|
|
21841
22482
|
if (entry.isDirectory()) {
|
|
21842
22483
|
componentDirs.push(componentsBase + "/" + entry.name);
|
|
@@ -21846,12 +22487,12 @@ function scanComponentExports(dataDb) {
|
|
|
21846
22487
|
}
|
|
21847
22488
|
}
|
|
21848
22489
|
for (const dir of componentDirs) {
|
|
21849
|
-
const absDir =
|
|
21850
|
-
if (!
|
|
22490
|
+
const absDir = resolve31(projectRoot, dir);
|
|
22491
|
+
if (!existsSync34(absDir)) continue;
|
|
21851
22492
|
const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
|
|
21852
22493
|
for (const file of files) {
|
|
21853
22494
|
const relPath = relative11(projectRoot, file);
|
|
21854
|
-
const source =
|
|
22495
|
+
const source = readFileSync35(file, "utf-8");
|
|
21855
22496
|
const annotations = parseFeatureAnnotations(source);
|
|
21856
22497
|
if (annotations.length > 0) {
|
|
21857
22498
|
for (const ann of annotations) {
|
|
@@ -21877,7 +22518,7 @@ function scanComponentExports(dataDb) {
|
|
|
21877
22518
|
if (hasHandlers && exportMatch) {
|
|
21878
22519
|
const componentName = exportMatch[1];
|
|
21879
22520
|
const domain = inferDomain(relPath);
|
|
21880
|
-
const subdomain = basename7(
|
|
22521
|
+
const subdomain = basename7(dirname15(relPath));
|
|
21881
22522
|
const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
|
|
21882
22523
|
if (!annotations.some((a2) => a2.featureKey === featureKey)) {
|
|
21883
22524
|
features.push({
|
|
@@ -21900,9 +22541,9 @@ function scanComponentExports(dataDb) {
|
|
|
21900
22541
|
function walkDir2(dir) {
|
|
21901
22542
|
const results = [];
|
|
21902
22543
|
try {
|
|
21903
|
-
const entries =
|
|
22544
|
+
const entries = readdirSync23(dir);
|
|
21904
22545
|
for (const entry of entries) {
|
|
21905
|
-
const fullPath =
|
|
22546
|
+
const fullPath = join21(dir, entry);
|
|
21906
22547
|
try {
|
|
21907
22548
|
const stat = statSync13(fullPath);
|
|
21908
22549
|
if (stat.isDirectory()) {
|
|
@@ -22890,7 +23531,7 @@ var init_audit_trail = __esm({
|
|
|
22890
23531
|
});
|
|
22891
23532
|
|
|
22892
23533
|
// src/validation-engine.ts
|
|
22893
|
-
import { existsSync as
|
|
23534
|
+
import { existsSync as existsSync35, readFileSync as readFileSync36 } from "fs";
|
|
22894
23535
|
function p10(baseName) {
|
|
22895
23536
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
22896
23537
|
}
|
|
@@ -22921,7 +23562,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
22921
23562
|
});
|
|
22922
23563
|
return checks;
|
|
22923
23564
|
}
|
|
22924
|
-
if (!
|
|
23565
|
+
if (!existsSync35(absPath)) {
|
|
22925
23566
|
checks.push({
|
|
22926
23567
|
name: "file_exists",
|
|
22927
23568
|
severity: "error",
|
|
@@ -22930,7 +23571,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
22930
23571
|
});
|
|
22931
23572
|
return checks;
|
|
22932
23573
|
}
|
|
22933
|
-
const source =
|
|
23574
|
+
const source = readFileSync36(absPath, "utf-8");
|
|
22934
23575
|
const lines = source.split("\n");
|
|
22935
23576
|
if (activeChecks.rule_compliance !== false) {
|
|
22936
23577
|
for (const ruleSet of config.rules) {
|
|
@@ -23375,7 +24016,7 @@ var init_adr_generator = __esm({
|
|
|
23375
24016
|
});
|
|
23376
24017
|
|
|
23377
24018
|
// src/security-scorer.ts
|
|
23378
|
-
import { existsSync as
|
|
24019
|
+
import { existsSync as existsSync36, readFileSync as readFileSync37 } from "fs";
|
|
23379
24020
|
function p12(baseName) {
|
|
23380
24021
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
23381
24022
|
}
|
|
@@ -23399,12 +24040,12 @@ function scoreFileSecurity(filePath, projectRoot) {
|
|
|
23399
24040
|
}]
|
|
23400
24041
|
};
|
|
23401
24042
|
}
|
|
23402
|
-
if (!
|
|
24043
|
+
if (!existsSync36(absPath)) {
|
|
23403
24044
|
return { riskScore: 0, findings: [] };
|
|
23404
24045
|
}
|
|
23405
24046
|
let source;
|
|
23406
24047
|
try {
|
|
23407
|
-
source =
|
|
24048
|
+
source = readFileSync37(absPath, "utf-8");
|
|
23408
24049
|
} catch {
|
|
23409
24050
|
return { riskScore: 0, findings: [] };
|
|
23410
24051
|
}
|
|
@@ -23693,8 +24334,8 @@ var init_security_scorer = __esm({
|
|
|
23693
24334
|
});
|
|
23694
24335
|
|
|
23695
24336
|
// src/dependency-scorer.ts
|
|
23696
|
-
import { existsSync as
|
|
23697
|
-
import { resolve as
|
|
24337
|
+
import { existsSync as existsSync37, readFileSync as readFileSync38 } from "fs";
|
|
24338
|
+
import { resolve as resolve32 } from "path";
|
|
23698
24339
|
function p13(baseName) {
|
|
23699
24340
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
23700
24341
|
}
|
|
@@ -23726,10 +24367,10 @@ function calculateDepRisk(factors) {
|
|
|
23726
24367
|
return Math.min(100, risk);
|
|
23727
24368
|
}
|
|
23728
24369
|
function getInstalledPackages(projectRoot) {
|
|
23729
|
-
const pkgPath =
|
|
23730
|
-
if (!
|
|
24370
|
+
const pkgPath = resolve32(projectRoot, "package.json");
|
|
24371
|
+
if (!existsSync37(pkgPath)) return /* @__PURE__ */ new Map();
|
|
23731
24372
|
try {
|
|
23732
|
-
const pkg = JSON.parse(
|
|
24373
|
+
const pkg = JSON.parse(readFileSync38(pkgPath, "utf-8"));
|
|
23733
24374
|
const packages = /* @__PURE__ */ new Map();
|
|
23734
24375
|
for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
|
|
23735
24376
|
packages.set(name, version);
|
|
@@ -24331,9 +24972,9 @@ var init_regression_detector = __esm({
|
|
|
24331
24972
|
});
|
|
24332
24973
|
|
|
24333
24974
|
// src/knowledge-indexer.ts
|
|
24334
|
-
import { createHash as
|
|
24335
|
-
import { readFileSync as
|
|
24336
|
-
import { resolve as
|
|
24975
|
+
import { createHash as createHash9 } from "crypto";
|
|
24976
|
+
import { readFileSync as readFileSync39, readdirSync as readdirSync24, statSync as statSync14, existsSync as existsSync38 } from "fs";
|
|
24977
|
+
import { resolve as resolve33, relative as relative12, basename as basename8, extname as extname2 } from "path";
|
|
24337
24978
|
function getKnowledgePaths() {
|
|
24338
24979
|
const resolved = getResolvedPaths();
|
|
24339
24980
|
const config = getConfig();
|
|
@@ -24359,9 +25000,9 @@ function discoverMarkdownFiles(baseDir) {
|
|
|
24359
25000
|
const files = [];
|
|
24360
25001
|
function walk(dir) {
|
|
24361
25002
|
try {
|
|
24362
|
-
const entries =
|
|
25003
|
+
const entries = readdirSync24(dir, { withFileTypes: true });
|
|
24363
25004
|
for (const entry of entries) {
|
|
24364
|
-
const fullPath =
|
|
25005
|
+
const fullPath = resolve33(dir, entry.name);
|
|
24365
25006
|
if (entry.isDirectory()) {
|
|
24366
25007
|
if (entry.name === "archive" && dir.includes("session-state")) continue;
|
|
24367
25008
|
if (entry.name === "archive" && dir.includes("status")) continue;
|
|
@@ -24419,7 +25060,7 @@ function categorizeFile(filePath) {
|
|
|
24419
25060
|
return "root";
|
|
24420
25061
|
}
|
|
24421
25062
|
function hashContent2(content) {
|
|
24422
|
-
return
|
|
25063
|
+
return createHash9("sha256").update(content).digest("hex");
|
|
24423
25064
|
}
|
|
24424
25065
|
function parseCRTable(content) {
|
|
24425
25066
|
const rules = [];
|
|
@@ -24639,11 +25280,11 @@ function indexAllKnowledge(db) {
|
|
|
24639
25280
|
files.push(...memFiles);
|
|
24640
25281
|
} catch {
|
|
24641
25282
|
}
|
|
24642
|
-
if (
|
|
25283
|
+
if (existsSync38(paths.plansDir)) {
|
|
24643
25284
|
const planFiles = discoverMarkdownFiles(paths.plansDir);
|
|
24644
25285
|
files.push(...planFiles);
|
|
24645
25286
|
}
|
|
24646
|
-
if (
|
|
25287
|
+
if (existsSync38(paths.docsDir)) {
|
|
24647
25288
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
24648
25289
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
|
|
24649
25290
|
files.push(...docsFiles);
|
|
@@ -24686,8 +25327,8 @@ function indexAllKnowledge(db) {
|
|
|
24686
25327
|
} catch {
|
|
24687
25328
|
}
|
|
24688
25329
|
for (const filePath of files) {
|
|
24689
|
-
if (!
|
|
24690
|
-
const content =
|
|
25330
|
+
if (!existsSync38(filePath)) continue;
|
|
25331
|
+
const content = readFileSync39(filePath, "utf-8");
|
|
24691
25332
|
const hash = hashContent2(content);
|
|
24692
25333
|
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
25334
|
const category = categorizeFile(filePath);
|
|
@@ -24807,10 +25448,10 @@ function isKnowledgeStale(db) {
|
|
|
24807
25448
|
files.push(...discoverMarkdownFiles(paths.memoryDir));
|
|
24808
25449
|
} catch {
|
|
24809
25450
|
}
|
|
24810
|
-
if (
|
|
25451
|
+
if (existsSync38(paths.plansDir)) {
|
|
24811
25452
|
files.push(...discoverMarkdownFiles(paths.plansDir));
|
|
24812
25453
|
}
|
|
24813
|
-
if (
|
|
25454
|
+
if (existsSync38(paths.docsDir)) {
|
|
24814
25455
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
24815
25456
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
|
|
24816
25457
|
files.push(...docsFiles);
|
|
@@ -24839,8 +25480,8 @@ var init_knowledge_indexer = __esm({
|
|
|
24839
25480
|
});
|
|
24840
25481
|
|
|
24841
25482
|
// src/knowledge-tools.ts
|
|
24842
|
-
import { readFileSync as
|
|
24843
|
-
import { resolve as
|
|
25483
|
+
import { readFileSync as readFileSync40, writeFileSync as writeFileSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync25 } from "fs";
|
|
25484
|
+
import { resolve as resolve34, basename as basename9 } from "path";
|
|
24844
25485
|
function p16(baseName) {
|
|
24845
25486
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
24846
25487
|
}
|
|
@@ -25577,7 +26218,7 @@ function handleCorrect(db, args2) {
|
|
|
25577
26218
|
if (!wrong || !correction || !rule) {
|
|
25578
26219
|
return text15("Error: wrong, correction, and rule are all required.");
|
|
25579
26220
|
}
|
|
25580
|
-
const correctionsPath =
|
|
26221
|
+
const correctionsPath = resolve34(getResolvedPaths().memoryDir, "corrections.md");
|
|
25581
26222
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
25582
26223
|
const title = rule.slice(0, 60);
|
|
25583
26224
|
const entry = `
|
|
@@ -25590,7 +26231,7 @@ ${crRule ? `- **CR**: ${crRule}
|
|
|
25590
26231
|
`;
|
|
25591
26232
|
let existing = "";
|
|
25592
26233
|
try {
|
|
25593
|
-
existing =
|
|
26234
|
+
existing = readFileSync40(correctionsPath, "utf-8");
|
|
25594
26235
|
} catch {
|
|
25595
26236
|
}
|
|
25596
26237
|
const archiveIdx = existing.indexOf("## Archived");
|
|
@@ -25772,7 +26413,7 @@ function handleGaps(db, args2) {
|
|
|
25772
26413
|
} else if (checkType === "routers") {
|
|
25773
26414
|
try {
|
|
25774
26415
|
const routersDir = getResolvedPaths().routersDir;
|
|
25775
|
-
const routerFiles =
|
|
26416
|
+
const routerFiles = readdirSync25(routersDir).filter((f2) => f2.endsWith(".ts") && !f2.startsWith("_"));
|
|
25776
26417
|
lines.push(`| Router | Knowledge Hits | Status |`);
|
|
25777
26418
|
lines.push(`|--------|----------------|--------|`);
|
|
25778
26419
|
for (const file of routerFiles) {
|
|
@@ -25928,13 +26569,13 @@ var init_knowledge_tools = __esm({
|
|
|
25928
26569
|
|
|
25929
26570
|
// src/knowledge-db.ts
|
|
25930
26571
|
import Database3 from "better-sqlite3";
|
|
25931
|
-
import { dirname as
|
|
25932
|
-
import { existsSync as
|
|
26572
|
+
import { dirname as dirname16 } from "path";
|
|
26573
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync11 } from "fs";
|
|
25933
26574
|
function getKnowledgeDb() {
|
|
25934
26575
|
const dbPath = getResolvedPaths().knowledgeDbPath;
|
|
25935
|
-
const dir =
|
|
25936
|
-
if (!
|
|
25937
|
-
|
|
26576
|
+
const dir = dirname16(dbPath);
|
|
26577
|
+
if (!existsSync40(dir)) {
|
|
26578
|
+
mkdirSync11(dir, { recursive: true });
|
|
25938
26579
|
}
|
|
25939
26580
|
const db = new Database3(dbPath);
|
|
25940
26581
|
db.pragma("journal_mode = WAL");
|
|
@@ -26667,8 +27308,8 @@ var init_python_tools = __esm({
|
|
|
26667
27308
|
});
|
|
26668
27309
|
|
|
26669
27310
|
// src/tools.ts
|
|
26670
|
-
import { readFileSync as
|
|
26671
|
-
import { resolve as
|
|
27311
|
+
import { readFileSync as readFileSync41, existsSync as existsSync41 } from "fs";
|
|
27312
|
+
import { resolve as resolve35, basename as basename10 } from "path";
|
|
26672
27313
|
function prefix2() {
|
|
26673
27314
|
return getConfig().toolPrefix;
|
|
26674
27315
|
}
|
|
@@ -26703,7 +27344,7 @@ function ensureIndexes(dataDb, codegraphDb, force = false) {
|
|
|
26703
27344
|
if (config.python?.root) {
|
|
26704
27345
|
const pythonRoot = config.python.root;
|
|
26705
27346
|
const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
|
|
26706
|
-
if (force || isPythonDataStale(dataDb,
|
|
27347
|
+
if (force || isPythonDataStale(dataDb, resolve35(getProjectRoot(), pythonRoot))) {
|
|
26707
27348
|
const pyImports = buildPythonImportIndex(dataDb, pythonRoot, excludeDirs);
|
|
26708
27349
|
results.push(`Python imports: ${pyImports}`);
|
|
26709
27350
|
const pyRoutes = buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs);
|
|
@@ -27138,9 +27779,9 @@ function handleContext(file, dataDb, codegraphDb) {
|
|
|
27138
27779
|
try {
|
|
27139
27780
|
const resolvedPaths = getResolvedPaths();
|
|
27140
27781
|
const root = getProjectRoot();
|
|
27141
|
-
const absFilePath = ensureWithinRoot(
|
|
27142
|
-
if (
|
|
27143
|
-
const fileContent =
|
|
27782
|
+
const absFilePath = ensureWithinRoot(resolve35(resolvedPaths.srcDir, "..", file), root);
|
|
27783
|
+
if (existsSync41(absFilePath)) {
|
|
27784
|
+
const fileContent = readFileSync41(absFilePath, "utf-8").slice(0, 3e3);
|
|
27144
27785
|
const keywords = [];
|
|
27145
27786
|
if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
|
|
27146
27787
|
if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
|
|
@@ -27564,11 +28205,11 @@ function handleSchema(args2) {
|
|
|
27564
28205
|
lines.push("Checking all column references against Prisma schema...");
|
|
27565
28206
|
lines.push("");
|
|
27566
28207
|
const projectRoot = getProjectRoot();
|
|
27567
|
-
const absPath = ensureWithinRoot(
|
|
27568
|
-
if (!
|
|
28208
|
+
const absPath = ensureWithinRoot(resolve35(projectRoot, file), projectRoot);
|
|
28209
|
+
if (!existsSync41(absPath)) {
|
|
27569
28210
|
return text17(`File not found: ${file}`);
|
|
27570
28211
|
}
|
|
27571
|
-
const source =
|
|
28212
|
+
const source = readFileSync41(absPath, "utf-8");
|
|
27572
28213
|
const config = getConfig();
|
|
27573
28214
|
const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
|
|
27574
28215
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
@@ -27937,8 +28578,8 @@ var init_server_dispatch = __esm({
|
|
|
27937
28578
|
|
|
27938
28579
|
// src/server.ts
|
|
27939
28580
|
var server_exports = {};
|
|
27940
|
-
import { readFileSync as
|
|
27941
|
-
import { resolve as
|
|
28581
|
+
import { readFileSync as readFileSync42 } from "fs";
|
|
28582
|
+
import { resolve as resolve36, dirname as dirname17 } from "path";
|
|
27942
28583
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
27943
28584
|
function pruneMemoryOnStartup() {
|
|
27944
28585
|
try {
|
|
@@ -27970,10 +28611,10 @@ var init_server = __esm({
|
|
|
27970
28611
|
init_memory_db();
|
|
27971
28612
|
init_license();
|
|
27972
28613
|
init_server_dispatch();
|
|
27973
|
-
__dirname4 =
|
|
28614
|
+
__dirname4 = dirname17(fileURLToPath4(import.meta.url));
|
|
27974
28615
|
PKG_VERSION = (() => {
|
|
27975
28616
|
try {
|
|
27976
|
-
const pkg = JSON.parse(
|
|
28617
|
+
const pkg = JSON.parse(readFileSync42(resolve36(__dirname4, "..", "package.json"), "utf-8"));
|
|
27977
28618
|
return pkg.version ?? "0.0.0";
|
|
27978
28619
|
} catch {
|
|
27979
28620
|
return "0.0.0";
|
|
@@ -28259,19 +28900,19 @@ var config_upgrade_exports = {};
|
|
|
28259
28900
|
__export(config_upgrade_exports, {
|
|
28260
28901
|
runConfigUpgrade: () => runConfigUpgrade
|
|
28261
28902
|
});
|
|
28262
|
-
import { existsSync as
|
|
28263
|
-
import { resolve as
|
|
28903
|
+
import { existsSync as existsSync42, readFileSync as readFileSync43, writeFileSync as writeFileSync6, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
|
|
28904
|
+
import { resolve as resolve37 } from "path";
|
|
28264
28905
|
import { parse as parseYaml6 } from "yaml";
|
|
28265
28906
|
async function runConfigUpgrade(opts = {}) {
|
|
28266
28907
|
const cwd = opts.cwd ?? process.cwd();
|
|
28267
|
-
const configPath =
|
|
28908
|
+
const configPath = resolve37(cwd, "massu.config.yaml");
|
|
28268
28909
|
const bakPath = `${configPath}.bak`;
|
|
28269
28910
|
const log = opts.silent ? () => {
|
|
28270
28911
|
} : (s) => process.stdout.write(s);
|
|
28271
28912
|
const err = opts.silent ? () => {
|
|
28272
28913
|
} : (s) => process.stderr.write(s);
|
|
28273
28914
|
if (opts.rollback) {
|
|
28274
|
-
if (!
|
|
28915
|
+
if (!existsSync42(bakPath)) {
|
|
28275
28916
|
const message = `No backup found at ${bakPath}`;
|
|
28276
28917
|
err(message + "\n");
|
|
28277
28918
|
return { exitCode: 1, action: "none", message };
|
|
@@ -28287,14 +28928,14 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
28287
28928
|
return { exitCode: 2, action: "none", message };
|
|
28288
28929
|
}
|
|
28289
28930
|
}
|
|
28290
|
-
if (!
|
|
28931
|
+
if (!existsSync42(configPath)) {
|
|
28291
28932
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
28292
28933
|
err(message + "\n");
|
|
28293
28934
|
return { exitCode: 1, action: "none", message };
|
|
28294
28935
|
}
|
|
28295
28936
|
let existing;
|
|
28296
28937
|
try {
|
|
28297
|
-
const content =
|
|
28938
|
+
const content = readFileSync43(configPath, "utf-8");
|
|
28298
28939
|
const parsed = parseYaml6(content);
|
|
28299
28940
|
if (!parsed || typeof parsed !== "object") {
|
|
28300
28941
|
throw new Error("config is not a YAML object");
|
|
@@ -28317,7 +28958,7 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
28317
28958
|
fingerprint: computeFingerprint(detection)
|
|
28318
28959
|
};
|
|
28319
28960
|
try {
|
|
28320
|
-
const original =
|
|
28961
|
+
const original = readFileSync43(configPath, "utf-8");
|
|
28321
28962
|
writeFileSync6(bakPath, original, "utf-8");
|
|
28322
28963
|
} catch (e2) {
|
|
28323
28964
|
const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
@@ -28351,8 +28992,8 @@ var config_check_drift_exports = {};
|
|
|
28351
28992
|
__export(config_check_drift_exports, {
|
|
28352
28993
|
runConfigCheckDrift: () => runConfigCheckDrift
|
|
28353
28994
|
});
|
|
28354
|
-
import { existsSync as
|
|
28355
|
-
import { resolve as
|
|
28995
|
+
import { existsSync as existsSync43, readFileSync as readFileSync44 } from "fs";
|
|
28996
|
+
import { resolve as resolve38 } from "path";
|
|
28356
28997
|
import { parse as parseYaml7 } from "yaml";
|
|
28357
28998
|
function renderChanges(changes) {
|
|
28358
28999
|
if (changes.length === 0) return "(none)\n";
|
|
@@ -28360,12 +29001,12 @@ function renderChanges(changes) {
|
|
|
28360
29001
|
}
|
|
28361
29002
|
async function runConfigCheckDrift(opts = {}) {
|
|
28362
29003
|
const cwd = opts.cwd ?? process.cwd();
|
|
28363
|
-
const configPath =
|
|
29004
|
+
const configPath = resolve38(cwd, "massu.config.yaml");
|
|
28364
29005
|
const log = opts.silent ? () => {
|
|
28365
29006
|
} : (s) => process.stdout.write(s);
|
|
28366
29007
|
const err = opts.silent ? () => {
|
|
28367
29008
|
} : (s) => process.stderr.write(s);
|
|
28368
|
-
if (!
|
|
29009
|
+
if (!existsSync43(configPath)) {
|
|
28369
29010
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
28370
29011
|
err(message + "\n");
|
|
28371
29012
|
return {
|
|
@@ -28379,7 +29020,7 @@ async function runConfigCheckDrift(opts = {}) {
|
|
|
28379
29020
|
}
|
|
28380
29021
|
let config;
|
|
28381
29022
|
try {
|
|
28382
|
-
const content =
|
|
29023
|
+
const content = readFileSync44(configPath, "utf-8");
|
|
28383
29024
|
const parsed = parseYaml7(content);
|
|
28384
29025
|
if (!parsed || typeof parsed !== "object") {
|
|
28385
29026
|
throw new Error("config is not a YAML object");
|
|
@@ -28446,11 +29087,11 @@ var init_config_check_drift = __esm({
|
|
|
28446
29087
|
});
|
|
28447
29088
|
|
|
28448
29089
|
// src/cli.ts
|
|
28449
|
-
import { readFileSync as
|
|
28450
|
-
import { resolve as
|
|
29090
|
+
import { readFileSync as readFileSync45 } from "fs";
|
|
29091
|
+
import { resolve as resolve39, dirname as dirname18 } from "path";
|
|
28451
29092
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
28452
29093
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
28453
|
-
var __dirname5 =
|
|
29094
|
+
var __dirname5 = dirname18(__filename4);
|
|
28454
29095
|
var args = process.argv.slice(2);
|
|
28455
29096
|
var subcommand = args[0];
|
|
28456
29097
|
async function main() {
|
|
@@ -28475,6 +29116,18 @@ async function main() {
|
|
|
28475
29116
|
await runInstallCommands2();
|
|
28476
29117
|
break;
|
|
28477
29118
|
}
|
|
29119
|
+
case "permissions": {
|
|
29120
|
+
const { handlePermissionsSubcommand: handlePermissionsSubcommand2 } = await Promise.resolve().then(() => (init_permissions2(), permissions_exports));
|
|
29121
|
+
const result = await handlePermissionsSubcommand2(args.slice(1));
|
|
29122
|
+
process.exit(result.exitCode);
|
|
29123
|
+
return;
|
|
29124
|
+
}
|
|
29125
|
+
case "changelog": {
|
|
29126
|
+
const { handleChangelogSubcommand: handleChangelogSubcommand2 } = await Promise.resolve().then(() => (init_changelog(), changelog_exports));
|
|
29127
|
+
const result = await handleChangelogSubcommand2(args.slice(1));
|
|
29128
|
+
process.exit(result.exitCode);
|
|
29129
|
+
return;
|
|
29130
|
+
}
|
|
28478
29131
|
case "show-template": {
|
|
28479
29132
|
const { runShowTemplate: runShowTemplate2 } = await Promise.resolve().then(() => (init_show_template(), show_template_exports));
|
|
28480
29133
|
await runShowTemplate2(args.slice(1));
|
|
@@ -28587,12 +29240,14 @@ Commands:
|
|
|
28587
29240
|
init Set up Massu AI in your project (one command, full setup)
|
|
28588
29241
|
doctor Check installation health
|
|
28589
29242
|
install-hooks Install/update Claude Code hooks
|
|
28590
|
-
install-commands Install/update slash commands
|
|
29243
|
+
install-commands Install/update slash commands (use --skip-permissions to opt out of MCP allowlist seeding)
|
|
28591
29244
|
show-template Print the resolved variant of a bundled template (e.g. for diffs)
|
|
28592
29245
|
watch Run the file-watcher daemon (auto-refresh on stack changes)
|
|
28593
29246
|
refresh-log [N] Show the last N watcher auto-refresh events
|
|
28594
29247
|
validate-config Validate massu.config.yaml (alias: config validate)
|
|
28595
29248
|
config <sub> Config lifecycle: refresh | validate | upgrade | doctor | check-drift
|
|
29249
|
+
permissions <sub> MCP permission lifecycle: install | verify | check-drift
|
|
29250
|
+
changelog <sub> CHANGELOG generation / verification: generate | verify
|
|
28596
29251
|
adapters <sub> Third-party adapter registry: list | refresh | search | add-local | remove-local | install | resign
|
|
28597
29252
|
|
|
28598
29253
|
Options:
|
|
@@ -28632,7 +29287,7 @@ Examples:
|
|
|
28632
29287
|
}
|
|
28633
29288
|
function printVersion() {
|
|
28634
29289
|
try {
|
|
28635
|
-
const pkg = JSON.parse(
|
|
29290
|
+
const pkg = JSON.parse(readFileSync45(resolve39(__dirname5, "../package.json"), "utf-8"));
|
|
28636
29291
|
console.log(`massu v${pkg.version}`);
|
|
28637
29292
|
} catch {
|
|
28638
29293
|
console.log("massu v0.1.0");
|