@massu/core 1.15.1 → 1.15.2
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/CHANGELOG.md +13 -0
- package/dist/cli.js +900 -572
- package/dist/hooks/auto-learning-pipeline.js +90 -19
- package/dist/hooks/classify-failure.js +112 -43
- package/dist/hooks/cost-tracker.js +108 -39
- package/dist/hooks/fix-detector.js +95 -24
- package/dist/hooks/incident-pipeline.js +114 -45
- package/dist/hooks/post-edit-context.js +105 -36
- package/dist/hooks/post-tool-use.js +137 -68
- package/dist/hooks/pre-compact.js +108 -39
- package/dist/hooks/pre-delete-check.js +107 -38
- package/dist/hooks/pre-tool-use-gate.js +113 -44
- package/dist/hooks/quality-event.js +108 -39
- package/dist/hooks/rule-enforcement-pipeline.js +92 -21
- package/dist/hooks/session-end.js +126 -57
- package/dist/hooks/session-start.js +165 -96
- package/dist/hooks/user-prompt.js +124 -55
- package/package.json +1 -1
|
@@ -10,12 +10,12 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
10
10
|
// src/memory-db.ts
|
|
11
11
|
import Database from "better-sqlite3";
|
|
12
12
|
import { dirname as dirname2, basename } from "path";
|
|
13
|
-
import { existsSync as
|
|
13
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
14
14
|
|
|
15
15
|
// src/config.ts
|
|
16
|
-
import { resolve, dirname } from "path";
|
|
17
|
-
import { existsSync, readFileSync } from "fs";
|
|
18
|
-
import { homedir } from "os";
|
|
16
|
+
import { resolve as resolve2, dirname } from "path";
|
|
17
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
18
|
+
import { homedir as homedir2 } from "os";
|
|
19
19
|
import { parse as parseYaml } from "yaml";
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
|
|
@@ -24,6 +24,68 @@ function encodeMemoryDirName(projectRoot) {
|
|
|
24
24
|
return projectRoot.replace(/\//g, "-");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// src/credentials.ts
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
import { resolve } from "path";
|
|
30
|
+
import {
|
|
31
|
+
existsSync,
|
|
32
|
+
readFileSync,
|
|
33
|
+
writeFileSync,
|
|
34
|
+
mkdirSync,
|
|
35
|
+
rmSync,
|
|
36
|
+
chmodSync
|
|
37
|
+
} from "fs";
|
|
38
|
+
var MASSU_ENV_API_KEY = "MASSU_API_KEY";
|
|
39
|
+
var MASSU_ENV_CLOUD_ENDPOINT = "MASSU_CLOUD_ENDPOINT";
|
|
40
|
+
var DEFAULT_CLOUD_ENDPOINT = "https://api.massu.ai/v1";
|
|
41
|
+
function credentialsDir(home = homedir()) {
|
|
42
|
+
return resolve(home, ".massu");
|
|
43
|
+
}
|
|
44
|
+
function credentialsPath(home = homedir()) {
|
|
45
|
+
return resolve(credentialsDir(home), "credentials");
|
|
46
|
+
}
|
|
47
|
+
function readUserCredentials(home = homedir()) {
|
|
48
|
+
try {
|
|
49
|
+
const p = credentialsPath(home);
|
|
50
|
+
if (!existsSync(p)) return void 0;
|
|
51
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8"));
|
|
52
|
+
const key = typeof parsed?.apiKey === "string" ? parsed.apiKey.trim() : "";
|
|
53
|
+
return key.length > 0 ? key : void 0;
|
|
54
|
+
} catch {
|
|
55
|
+
return void 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function isUnresolvedLiteral(v) {
|
|
59
|
+
return /^\$\{[^}]+\}$/.test(v);
|
|
60
|
+
}
|
|
61
|
+
function resolveApiKey(opts = {}) {
|
|
62
|
+
const env = opts.env ?? process.env;
|
|
63
|
+
const home = opts.home ?? homedir();
|
|
64
|
+
const cfg = typeof opts.configApiKey === "string" ? opts.configApiKey.trim() : "";
|
|
65
|
+
if (cfg.length > 0 && !isUnresolvedLiteral(cfg)) {
|
|
66
|
+
return { apiKey: cfg, source: "config" };
|
|
67
|
+
}
|
|
68
|
+
const envRaw = env[MASSU_ENV_API_KEY];
|
|
69
|
+
const envKey = typeof envRaw === "string" ? envRaw.trim() : "";
|
|
70
|
+
if (envKey.length > 0) {
|
|
71
|
+
return { apiKey: envKey, source: "env" };
|
|
72
|
+
}
|
|
73
|
+
const fileKey = readUserCredentials(home);
|
|
74
|
+
if (fileKey) {
|
|
75
|
+
return { apiKey: fileKey, source: "user-file" };
|
|
76
|
+
}
|
|
77
|
+
return { source: "none" };
|
|
78
|
+
}
|
|
79
|
+
function resolveEndpoint(opts = {}) {
|
|
80
|
+
const env = opts.env ?? process.env;
|
|
81
|
+
const cfg = typeof opts.configEndpoint === "string" ? opts.configEndpoint.trim() : "";
|
|
82
|
+
if (cfg.length > 0) return cfg;
|
|
83
|
+
const envRaw = env[MASSU_ENV_CLOUD_ENDPOINT];
|
|
84
|
+
const envEp = typeof envRaw === "string" ? envRaw.trim() : "";
|
|
85
|
+
if (envEp.length > 0) return envEp;
|
|
86
|
+
return DEFAULT_CLOUD_ENDPOINT;
|
|
87
|
+
}
|
|
88
|
+
|
|
27
89
|
// src/config.ts
|
|
28
90
|
var DomainConfigSchema = z.object({
|
|
29
91
|
name: z.string().default("Unknown"),
|
|
@@ -406,12 +468,13 @@ var RawConfigSchema = z.object({
|
|
|
406
468
|
lsp: LSPConfigSchema.optional()
|
|
407
469
|
}).passthrough();
|
|
408
470
|
var _config = null;
|
|
471
|
+
var _resolvedApiKeySource = "none";
|
|
409
472
|
var _projectRoot = null;
|
|
410
473
|
function findProjectRoot() {
|
|
411
474
|
const cwd = process.cwd();
|
|
412
475
|
let dir = cwd;
|
|
413
476
|
while (true) {
|
|
414
|
-
if (
|
|
477
|
+
if (existsSync2(resolve2(dir, "massu.config.yaml"))) {
|
|
415
478
|
return dir;
|
|
416
479
|
}
|
|
417
480
|
const parent = dirname(dir);
|
|
@@ -420,10 +483,10 @@ function findProjectRoot() {
|
|
|
420
483
|
}
|
|
421
484
|
dir = cwd;
|
|
422
485
|
while (true) {
|
|
423
|
-
if (
|
|
486
|
+
if (existsSync2(resolve2(dir, "package.json"))) {
|
|
424
487
|
return dir;
|
|
425
488
|
}
|
|
426
|
-
if (
|
|
489
|
+
if (existsSync2(resolve2(dir, ".git"))) {
|
|
427
490
|
return dir;
|
|
428
491
|
}
|
|
429
492
|
const parent = dirname(dir);
|
|
@@ -441,10 +504,10 @@ function getProjectRoot() {
|
|
|
441
504
|
function getConfig() {
|
|
442
505
|
if (_config) return _config;
|
|
443
506
|
const root = getProjectRoot();
|
|
444
|
-
const configPath =
|
|
507
|
+
const configPath = resolve2(root, "massu.config.yaml");
|
|
445
508
|
let rawYaml = {};
|
|
446
|
-
if (
|
|
447
|
-
const content =
|
|
509
|
+
if (existsSync2(configPath)) {
|
|
510
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
448
511
|
rawYaml = parseYaml(content) ?? {};
|
|
449
512
|
}
|
|
450
513
|
const result = RawConfigSchema.safeParse(rawYaml);
|
|
@@ -461,7 +524,7 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
461
524
|
);
|
|
462
525
|
}
|
|
463
526
|
const parsed = result.data;
|
|
464
|
-
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root :
|
|
527
|
+
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve2(root, parsed.project.root);
|
|
465
528
|
const fw = parsed.framework;
|
|
466
529
|
let router = fw.router;
|
|
467
530
|
let orm = fw.orm;
|
|
@@ -519,13 +582,19 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
519
582
|
telemetry: parsed.telemetry,
|
|
520
583
|
lsp: parsed.lsp
|
|
521
584
|
};
|
|
522
|
-
|
|
585
|
+
const resolvedKey = resolveApiKey({ configApiKey: _config.cloud?.apiKey });
|
|
586
|
+
_resolvedApiKeySource = resolvedKey.source;
|
|
587
|
+
const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
|
|
588
|
+
if (resolvedKey.apiKey) {
|
|
523
589
|
_config.cloud = {
|
|
524
590
|
enabled: true,
|
|
525
591
|
sync: { memory: true, analytics: true, audit: true },
|
|
526
592
|
..._config.cloud,
|
|
527
|
-
apiKey:
|
|
593
|
+
apiKey: resolvedKey.apiKey,
|
|
594
|
+
endpoint: resolvedEndpoint
|
|
528
595
|
};
|
|
596
|
+
} else if (_config.cloud) {
|
|
597
|
+
_config.cloud = { ..._config.cloud, endpoint: _config.cloud.endpoint ?? resolvedEndpoint };
|
|
529
598
|
}
|
|
530
599
|
return _config;
|
|
531
600
|
}
|
|
@@ -534,34 +603,34 @@ function getResolvedPaths() {
|
|
|
534
603
|
const root = getProjectRoot();
|
|
535
604
|
const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
|
|
536
605
|
return {
|
|
537
|
-
codegraphDbPath:
|
|
538
|
-
dataDbPath:
|
|
539
|
-
prismaSchemaPath:
|
|
540
|
-
rootRouterPath:
|
|
541
|
-
routersDir:
|
|
542
|
-
srcDir:
|
|
606
|
+
codegraphDbPath: resolve2(root, ".codegraph/codegraph.db"),
|
|
607
|
+
dataDbPath: resolve2(root, ".massu/data.db"),
|
|
608
|
+
prismaSchemaPath: resolve2(root, config.paths.schema ?? "prisma/schema.prisma"),
|
|
609
|
+
rootRouterPath: resolve2(root, config.paths.routerRoot ?? "src/server/api/root.ts"),
|
|
610
|
+
routersDir: resolve2(root, config.paths.routers ?? "src/server/api/routers"),
|
|
611
|
+
srcDir: resolve2(root, config.paths.source),
|
|
543
612
|
pathAlias: Object.fromEntries(
|
|
544
613
|
Object.entries(config.paths.aliases).map(([alias, target]) => [
|
|
545
614
|
alias,
|
|
546
|
-
|
|
615
|
+
resolve2(root, target)
|
|
547
616
|
])
|
|
548
617
|
),
|
|
549
618
|
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
550
619
|
indexFiles: ["index.ts", "index.tsx", "index.js", "index.jsx"],
|
|
551
|
-
patternsDir:
|
|
552
|
-
claudeMdPath:
|
|
553
|
-
docsMapPath:
|
|
554
|
-
helpSitePath:
|
|
555
|
-
memoryDbPath:
|
|
556
|
-
knowledgeDbPath:
|
|
557
|
-
plansDir:
|
|
558
|
-
docsDir:
|
|
559
|
-
claudeDir:
|
|
560
|
-
memoryDir:
|
|
561
|
-
sessionStatePath:
|
|
562
|
-
sessionArchivePath:
|
|
563
|
-
mcpJsonPath:
|
|
564
|
-
settingsLocalPath:
|
|
620
|
+
patternsDir: resolve2(root, claudeDirName, "patterns"),
|
|
621
|
+
claudeMdPath: resolve2(root, claudeDirName, "CLAUDE.md"),
|
|
622
|
+
docsMapPath: resolve2(root, ".massu/docs-map.json"),
|
|
623
|
+
helpSitePath: resolve2(root, "../" + config.project.name + "-help"),
|
|
624
|
+
memoryDbPath: resolve2(root, ".massu/memory.db"),
|
|
625
|
+
knowledgeDbPath: resolve2(root, ".massu/knowledge.db"),
|
|
626
|
+
plansDir: resolve2(root, "docs/plans"),
|
|
627
|
+
docsDir: resolve2(root, "docs"),
|
|
628
|
+
claudeDir: resolve2(root, claudeDirName),
|
|
629
|
+
memoryDir: resolve2(homedir2(), claudeDirName, "projects", encodeMemoryDirName(root), "memory"),
|
|
630
|
+
sessionStatePath: resolve2(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
|
|
631
|
+
sessionArchivePath: resolve2(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
|
|
632
|
+
mcpJsonPath: resolve2(root, ".mcp.json"),
|
|
633
|
+
settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
|
|
565
634
|
};
|
|
566
635
|
}
|
|
567
636
|
|
|
@@ -569,8 +638,8 @@ function getResolvedPaths() {
|
|
|
569
638
|
function getMemoryDb() {
|
|
570
639
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
571
640
|
const dir = dirname2(dbPath);
|
|
572
|
-
if (!
|
|
573
|
-
|
|
641
|
+
if (!existsSync3(dir)) {
|
|
642
|
+
mkdirSync2(dir, { recursive: true });
|
|
574
643
|
}
|
|
575
644
|
const db = new Database(dbPath);
|
|
576
645
|
db.pragma("journal_mode = WAL");
|
|
@@ -1315,7 +1384,7 @@ function detectDecisionPatterns(text) {
|
|
|
1315
1384
|
}
|
|
1316
1385
|
|
|
1317
1386
|
// src/observation-extractor.ts
|
|
1318
|
-
import { homedir as
|
|
1387
|
+
import { homedir as homedir3 } from "os";
|
|
1319
1388
|
var PRIVATE_PATTERNS = [
|
|
1320
1389
|
/\/Users\/\w+/,
|
|
1321
1390
|
// Absolute macOS paths
|
|
@@ -1506,7 +1575,7 @@ function shortenPath(filePath) {
|
|
|
1506
1575
|
if (filePath.startsWith(root + "/")) {
|
|
1507
1576
|
return filePath.slice(root.length + 1);
|
|
1508
1577
|
}
|
|
1509
|
-
const home =
|
|
1578
|
+
const home = homedir3();
|
|
1510
1579
|
if (filePath.startsWith(home + "/")) {
|
|
1511
1580
|
return "~/" + filePath.slice(home.length + 1);
|
|
1512
1581
|
}
|
|
@@ -1646,14 +1715,14 @@ function recordTestResult(db, featureKey, passing, failing) {
|
|
|
1646
1715
|
}
|
|
1647
1716
|
|
|
1648
1717
|
// src/import-resolver.ts
|
|
1649
|
-
import { readFileSync as
|
|
1650
|
-
import { resolve as
|
|
1718
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, statSync } from "fs";
|
|
1719
|
+
import { resolve as resolve5, dirname as dirname3, join } from "path";
|
|
1651
1720
|
|
|
1652
1721
|
// src/security-utils.ts
|
|
1653
|
-
import { resolve as
|
|
1722
|
+
import { resolve as resolve4, normalize } from "path";
|
|
1654
1723
|
function ensureWithinRoot(filePath, projectRoot) {
|
|
1655
|
-
const resolvedRoot =
|
|
1656
|
-
const resolvedPath =
|
|
1724
|
+
const resolvedRoot = resolve4(projectRoot);
|
|
1725
|
+
const resolvedPath = resolve4(resolvedRoot, filePath);
|
|
1657
1726
|
const normalizedPath = normalize(resolvedPath);
|
|
1658
1727
|
const normalizedRoot = normalize(resolvedRoot);
|
|
1659
1728
|
if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
|
|
@@ -1703,23 +1772,23 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
1703
1772
|
let basePath;
|
|
1704
1773
|
if (specifier.startsWith("@/")) {
|
|
1705
1774
|
const paths = getResolvedPaths();
|
|
1706
|
-
basePath =
|
|
1775
|
+
basePath = resolve5(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
|
|
1707
1776
|
} else {
|
|
1708
|
-
basePath =
|
|
1777
|
+
basePath = resolve5(dirname3(fromFile), specifier);
|
|
1709
1778
|
}
|
|
1710
|
-
if (
|
|
1779
|
+
if (existsSync4(basePath) && !isDirectory(basePath)) {
|
|
1711
1780
|
return toRelative(basePath);
|
|
1712
1781
|
}
|
|
1713
1782
|
const resolvedPaths = getResolvedPaths();
|
|
1714
1783
|
for (const ext of resolvedPaths.extensions) {
|
|
1715
1784
|
const withExt = basePath + ext;
|
|
1716
|
-
if (
|
|
1785
|
+
if (existsSync4(withExt)) {
|
|
1717
1786
|
return toRelative(withExt);
|
|
1718
1787
|
}
|
|
1719
1788
|
}
|
|
1720
1789
|
for (const indexFile of resolvedPaths.indexFiles) {
|
|
1721
1790
|
const indexPath = join(basePath, indexFile);
|
|
1722
|
-
if (
|
|
1791
|
+
if (existsSync4(indexPath)) {
|
|
1723
1792
|
return toRelative(indexPath);
|
|
1724
1793
|
}
|
|
1725
1794
|
}
|
|
@@ -1741,7 +1810,7 @@ function toRelative(absPath) {
|
|
|
1741
1810
|
}
|
|
1742
1811
|
|
|
1743
1812
|
// src/validation-engine.ts
|
|
1744
|
-
import { existsSync as
|
|
1813
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1745
1814
|
function getValidationChecks() {
|
|
1746
1815
|
return getConfig().governance?.validation?.checks ?? {
|
|
1747
1816
|
rule_compliance: true,
|
|
@@ -1769,7 +1838,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
1769
1838
|
});
|
|
1770
1839
|
return checks;
|
|
1771
1840
|
}
|
|
1772
|
-
if (!
|
|
1841
|
+
if (!existsSync5(absPath)) {
|
|
1773
1842
|
checks.push({
|
|
1774
1843
|
name: "file_exists",
|
|
1775
1844
|
severity: "error",
|
|
@@ -1778,7 +1847,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
1778
1847
|
});
|
|
1779
1848
|
return checks;
|
|
1780
1849
|
}
|
|
1781
|
-
const source =
|
|
1850
|
+
const source = readFileSync4(absPath, "utf-8");
|
|
1782
1851
|
const lines = source.split("\n");
|
|
1783
1852
|
if (activeChecks.rule_compliance !== false) {
|
|
1784
1853
|
for (const ruleSet of config.rules) {
|
|
@@ -1876,7 +1945,7 @@ function storeValidationResult(db, filePath, checks, sessionId, validationType =
|
|
|
1876
1945
|
}
|
|
1877
1946
|
|
|
1878
1947
|
// src/security-scorer.ts
|
|
1879
|
-
import { existsSync as
|
|
1948
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1880
1949
|
var DEFAULT_SECURITY_PATTERNS = [
|
|
1881
1950
|
{
|
|
1882
1951
|
regex: /\bexec\s*\(\s*[`"'].*\$\{/,
|
|
@@ -1967,12 +2036,12 @@ function scoreFileSecurity(filePath, projectRoot) {
|
|
|
1967
2036
|
}]
|
|
1968
2037
|
};
|
|
1969
2038
|
}
|
|
1970
|
-
if (!
|
|
2039
|
+
if (!existsSync6(absPath)) {
|
|
1971
2040
|
return { riskScore: 0, findings: [] };
|
|
1972
2041
|
}
|
|
1973
2042
|
let source;
|
|
1974
2043
|
try {
|
|
1975
|
-
source =
|
|
2044
|
+
source = readFileSync5(absPath, "utf-8");
|
|
1976
2045
|
} catch {
|
|
1977
2046
|
return { riskScore: 0, findings: [] };
|
|
1978
2047
|
}
|
|
@@ -2011,7 +2080,7 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
|
|
|
2011
2080
|
}
|
|
2012
2081
|
|
|
2013
2082
|
// src/hooks/post-tool-use.ts
|
|
2014
|
-
import { readFileSync as
|
|
2083
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3, statSync as statSync2 } from "fs";
|
|
2015
2084
|
import { join as join2 } from "path";
|
|
2016
2085
|
|
|
2017
2086
|
// src/lib/recurrence-incrementer.ts
|
|
@@ -2073,11 +2142,11 @@ function incrementRecurrenceCountsForScannerFailures(db, sessionId, scannerStdou
|
|
|
2073
2142
|
}
|
|
2074
2143
|
|
|
2075
2144
|
// src/memory-file-ingest.ts
|
|
2076
|
-
import { readFileSync as
|
|
2145
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7, readdirSync } from "fs";
|
|
2077
2146
|
import { parse as parseYaml2 } from "yaml";
|
|
2078
2147
|
function ingestMemoryFile(db, sessionId, filePath) {
|
|
2079
|
-
if (!
|
|
2080
|
-
const content =
|
|
2148
|
+
if (!existsSync7(filePath)) return "skipped";
|
|
2149
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
2081
2150
|
const basename2 = (filePath.split("/").pop() ?? "").replace(".md", "");
|
|
2082
2151
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2083
2152
|
let name = basename2;
|
|
@@ -2291,11 +2360,11 @@ async function main() {
|
|
|
2291
2360
|
try {
|
|
2292
2361
|
const projectRoot = process.cwd();
|
|
2293
2362
|
const dir = join2(projectRoot, ".massu", "rule-candidates");
|
|
2294
|
-
if (!
|
|
2363
|
+
if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
|
|
2295
2364
|
const logPath = join2(dir, ".cr53-increment-failures.jsonl");
|
|
2296
|
-
const pre =
|
|
2365
|
+
const pre = existsSync8(logPath) ? readFileSync7(logPath, "utf-8") : "";
|
|
2297
2366
|
const sep = pre && !pre.endsWith("\n") ? "\n" : "";
|
|
2298
|
-
|
|
2367
|
+
writeFileSync2(logPath, pre + sep + JSON.stringify({
|
|
2299
2368
|
session_id,
|
|
2300
2369
|
scanner_output_excerpt: (tool_response ?? "").slice(0, 200),
|
|
2301
2370
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -2377,14 +2446,14 @@ function parseTestRunOutput(output) {
|
|
|
2377
2446
|
return null;
|
|
2378
2447
|
}
|
|
2379
2448
|
function readStdin() {
|
|
2380
|
-
return new Promise((
|
|
2449
|
+
return new Promise((resolve6) => {
|
|
2381
2450
|
let data = "";
|
|
2382
2451
|
process.stdin.setEncoding("utf-8");
|
|
2383
2452
|
process.stdin.on("data", (chunk) => {
|
|
2384
2453
|
data += chunk;
|
|
2385
2454
|
});
|
|
2386
|
-
process.stdin.on("end", () =>
|
|
2387
|
-
setTimeout(() =>
|
|
2455
|
+
process.stdin.on("end", () => resolve6(data));
|
|
2456
|
+
setTimeout(() => resolve6(data), 3e3);
|
|
2388
2457
|
});
|
|
2389
2458
|
}
|
|
2390
2459
|
var _cachedConventions = null;
|
|
@@ -2398,12 +2467,12 @@ function readConventions(cwd) {
|
|
|
2398
2467
|
try {
|
|
2399
2468
|
const projectRoot = cwd ?? process.cwd();
|
|
2400
2469
|
const configPath = join2(projectRoot, "massu.config.yaml");
|
|
2401
|
-
if (!
|
|
2470
|
+
if (!existsSync8(configPath)) return _conventionDefaults;
|
|
2402
2471
|
const mtimeMs = statSync2(configPath).mtimeMs;
|
|
2403
2472
|
if (_cachedConventions !== null && _cachedConventionsPath === configPath && _cachedConventionsMtimeMs === mtimeMs) {
|
|
2404
2473
|
return _cachedConventions;
|
|
2405
2474
|
}
|
|
2406
|
-
const content =
|
|
2475
|
+
const content = readFileSync7(configPath, "utf-8");
|
|
2407
2476
|
const parsed = parseYaml3(content);
|
|
2408
2477
|
if (!parsed || typeof parsed !== "object") return _conventionDefaults;
|
|
2409
2478
|
const conventions = parsed.conventions;
|
|
@@ -2435,11 +2504,11 @@ function isKnowledgeSourceFile(filePath) {
|
|
|
2435
2504
|
function checkMemoryFileIntegrity(filePath) {
|
|
2436
2505
|
const issues = [];
|
|
2437
2506
|
try {
|
|
2438
|
-
if (!
|
|
2507
|
+
if (!existsSync8(filePath)) {
|
|
2439
2508
|
issues.push("MEMORY.md file does not exist after write");
|
|
2440
2509
|
return issues;
|
|
2441
2510
|
}
|
|
2442
|
-
const content =
|
|
2511
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
2443
2512
|
const lines = content.split("\n");
|
|
2444
2513
|
const MAX_LINES = 200;
|
|
2445
2514
|
if (lines.length > MAX_LINES) {
|
|
@@ -4,12 +4,12 @@ import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
|
|
|
4
4
|
// src/memory-db.ts
|
|
5
5
|
import Database from "better-sqlite3";
|
|
6
6
|
import { dirname as dirname2, basename } from "path";
|
|
7
|
-
import { existsSync as
|
|
7
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
10
|
-
import { resolve, dirname } from "path";
|
|
11
|
-
import { existsSync, readFileSync } from "fs";
|
|
12
|
-
import { homedir } from "os";
|
|
10
|
+
import { resolve as resolve2, dirname } from "path";
|
|
11
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
12
|
+
import { homedir as homedir2 } from "os";
|
|
13
13
|
import { parse as parseYaml } from "yaml";
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
|
|
@@ -18,6 +18,68 @@ function encodeMemoryDirName(projectRoot) {
|
|
|
18
18
|
return projectRoot.replace(/\//g, "-");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// src/credentials.ts
|
|
22
|
+
import { homedir } from "os";
|
|
23
|
+
import { resolve } from "path";
|
|
24
|
+
import {
|
|
25
|
+
existsSync,
|
|
26
|
+
readFileSync,
|
|
27
|
+
writeFileSync,
|
|
28
|
+
mkdirSync,
|
|
29
|
+
rmSync,
|
|
30
|
+
chmodSync
|
|
31
|
+
} from "fs";
|
|
32
|
+
var MASSU_ENV_API_KEY = "MASSU_API_KEY";
|
|
33
|
+
var MASSU_ENV_CLOUD_ENDPOINT = "MASSU_CLOUD_ENDPOINT";
|
|
34
|
+
var DEFAULT_CLOUD_ENDPOINT = "https://api.massu.ai/v1";
|
|
35
|
+
function credentialsDir(home = homedir()) {
|
|
36
|
+
return resolve(home, ".massu");
|
|
37
|
+
}
|
|
38
|
+
function credentialsPath(home = homedir()) {
|
|
39
|
+
return resolve(credentialsDir(home), "credentials");
|
|
40
|
+
}
|
|
41
|
+
function readUserCredentials(home = homedir()) {
|
|
42
|
+
try {
|
|
43
|
+
const p = credentialsPath(home);
|
|
44
|
+
if (!existsSync(p)) return void 0;
|
|
45
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8"));
|
|
46
|
+
const key = typeof parsed?.apiKey === "string" ? parsed.apiKey.trim() : "";
|
|
47
|
+
return key.length > 0 ? key : void 0;
|
|
48
|
+
} catch {
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function isUnresolvedLiteral(v) {
|
|
53
|
+
return /^\$\{[^}]+\}$/.test(v);
|
|
54
|
+
}
|
|
55
|
+
function resolveApiKey(opts = {}) {
|
|
56
|
+
const env = opts.env ?? process.env;
|
|
57
|
+
const home = opts.home ?? homedir();
|
|
58
|
+
const cfg = typeof opts.configApiKey === "string" ? opts.configApiKey.trim() : "";
|
|
59
|
+
if (cfg.length > 0 && !isUnresolvedLiteral(cfg)) {
|
|
60
|
+
return { apiKey: cfg, source: "config" };
|
|
61
|
+
}
|
|
62
|
+
const envRaw = env[MASSU_ENV_API_KEY];
|
|
63
|
+
const envKey = typeof envRaw === "string" ? envRaw.trim() : "";
|
|
64
|
+
if (envKey.length > 0) {
|
|
65
|
+
return { apiKey: envKey, source: "env" };
|
|
66
|
+
}
|
|
67
|
+
const fileKey = readUserCredentials(home);
|
|
68
|
+
if (fileKey) {
|
|
69
|
+
return { apiKey: fileKey, source: "user-file" };
|
|
70
|
+
}
|
|
71
|
+
return { source: "none" };
|
|
72
|
+
}
|
|
73
|
+
function resolveEndpoint(opts = {}) {
|
|
74
|
+
const env = opts.env ?? process.env;
|
|
75
|
+
const cfg = typeof opts.configEndpoint === "string" ? opts.configEndpoint.trim() : "";
|
|
76
|
+
if (cfg.length > 0) return cfg;
|
|
77
|
+
const envRaw = env[MASSU_ENV_CLOUD_ENDPOINT];
|
|
78
|
+
const envEp = typeof envRaw === "string" ? envRaw.trim() : "";
|
|
79
|
+
if (envEp.length > 0) return envEp;
|
|
80
|
+
return DEFAULT_CLOUD_ENDPOINT;
|
|
81
|
+
}
|
|
82
|
+
|
|
21
83
|
// src/config.ts
|
|
22
84
|
var DomainConfigSchema = z.object({
|
|
23
85
|
name: z.string().default("Unknown"),
|
|
@@ -400,12 +462,13 @@ var RawConfigSchema = z.object({
|
|
|
400
462
|
lsp: LSPConfigSchema.optional()
|
|
401
463
|
}).passthrough();
|
|
402
464
|
var _config = null;
|
|
465
|
+
var _resolvedApiKeySource = "none";
|
|
403
466
|
var _projectRoot = null;
|
|
404
467
|
function findProjectRoot() {
|
|
405
468
|
const cwd = process.cwd();
|
|
406
469
|
let dir = cwd;
|
|
407
470
|
while (true) {
|
|
408
|
-
if (
|
|
471
|
+
if (existsSync2(resolve2(dir, "massu.config.yaml"))) {
|
|
409
472
|
return dir;
|
|
410
473
|
}
|
|
411
474
|
const parent = dirname(dir);
|
|
@@ -414,10 +477,10 @@ function findProjectRoot() {
|
|
|
414
477
|
}
|
|
415
478
|
dir = cwd;
|
|
416
479
|
while (true) {
|
|
417
|
-
if (
|
|
480
|
+
if (existsSync2(resolve2(dir, "package.json"))) {
|
|
418
481
|
return dir;
|
|
419
482
|
}
|
|
420
|
-
if (
|
|
483
|
+
if (existsSync2(resolve2(dir, ".git"))) {
|
|
421
484
|
return dir;
|
|
422
485
|
}
|
|
423
486
|
const parent = dirname(dir);
|
|
@@ -435,10 +498,10 @@ function getProjectRoot() {
|
|
|
435
498
|
function getConfig() {
|
|
436
499
|
if (_config) return _config;
|
|
437
500
|
const root = getProjectRoot();
|
|
438
|
-
const configPath =
|
|
501
|
+
const configPath = resolve2(root, "massu.config.yaml");
|
|
439
502
|
let rawYaml = {};
|
|
440
|
-
if (
|
|
441
|
-
const content =
|
|
503
|
+
if (existsSync2(configPath)) {
|
|
504
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
442
505
|
rawYaml = parseYaml(content) ?? {};
|
|
443
506
|
}
|
|
444
507
|
const result = RawConfigSchema.safeParse(rawYaml);
|
|
@@ -455,7 +518,7 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
455
518
|
);
|
|
456
519
|
}
|
|
457
520
|
const parsed = result.data;
|
|
458
|
-
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root :
|
|
521
|
+
const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve2(root, parsed.project.root);
|
|
459
522
|
const fw = parsed.framework;
|
|
460
523
|
let router = fw.router;
|
|
461
524
|
let orm = fw.orm;
|
|
@@ -513,13 +576,19 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
513
576
|
telemetry: parsed.telemetry,
|
|
514
577
|
lsp: parsed.lsp
|
|
515
578
|
};
|
|
516
|
-
|
|
579
|
+
const resolvedKey = resolveApiKey({ configApiKey: _config.cloud?.apiKey });
|
|
580
|
+
_resolvedApiKeySource = resolvedKey.source;
|
|
581
|
+
const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
|
|
582
|
+
if (resolvedKey.apiKey) {
|
|
517
583
|
_config.cloud = {
|
|
518
584
|
enabled: true,
|
|
519
585
|
sync: { memory: true, analytics: true, audit: true },
|
|
520
586
|
..._config.cloud,
|
|
521
|
-
apiKey:
|
|
587
|
+
apiKey: resolvedKey.apiKey,
|
|
588
|
+
endpoint: resolvedEndpoint
|
|
522
589
|
};
|
|
590
|
+
} else if (_config.cloud) {
|
|
591
|
+
_config.cloud = { ..._config.cloud, endpoint: _config.cloud.endpoint ?? resolvedEndpoint };
|
|
523
592
|
}
|
|
524
593
|
return _config;
|
|
525
594
|
}
|
|
@@ -528,34 +597,34 @@ function getResolvedPaths() {
|
|
|
528
597
|
const root = getProjectRoot();
|
|
529
598
|
const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
|
|
530
599
|
return {
|
|
531
|
-
codegraphDbPath:
|
|
532
|
-
dataDbPath:
|
|
533
|
-
prismaSchemaPath:
|
|
534
|
-
rootRouterPath:
|
|
535
|
-
routersDir:
|
|
536
|
-
srcDir:
|
|
600
|
+
codegraphDbPath: resolve2(root, ".codegraph/codegraph.db"),
|
|
601
|
+
dataDbPath: resolve2(root, ".massu/data.db"),
|
|
602
|
+
prismaSchemaPath: resolve2(root, config.paths.schema ?? "prisma/schema.prisma"),
|
|
603
|
+
rootRouterPath: resolve2(root, config.paths.routerRoot ?? "src/server/api/root.ts"),
|
|
604
|
+
routersDir: resolve2(root, config.paths.routers ?? "src/server/api/routers"),
|
|
605
|
+
srcDir: resolve2(root, config.paths.source),
|
|
537
606
|
pathAlias: Object.fromEntries(
|
|
538
607
|
Object.entries(config.paths.aliases).map(([alias, target]) => [
|
|
539
608
|
alias,
|
|
540
|
-
|
|
609
|
+
resolve2(root, target)
|
|
541
610
|
])
|
|
542
611
|
),
|
|
543
612
|
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
544
613
|
indexFiles: ["index.ts", "index.tsx", "index.js", "index.jsx"],
|
|
545
|
-
patternsDir:
|
|
546
|
-
claudeMdPath:
|
|
547
|
-
docsMapPath:
|
|
548
|
-
helpSitePath:
|
|
549
|
-
memoryDbPath:
|
|
550
|
-
knowledgeDbPath:
|
|
551
|
-
plansDir:
|
|
552
|
-
docsDir:
|
|
553
|
-
claudeDir:
|
|
554
|
-
memoryDir:
|
|
555
|
-
sessionStatePath:
|
|
556
|
-
sessionArchivePath:
|
|
557
|
-
mcpJsonPath:
|
|
558
|
-
settingsLocalPath:
|
|
614
|
+
patternsDir: resolve2(root, claudeDirName, "patterns"),
|
|
615
|
+
claudeMdPath: resolve2(root, claudeDirName, "CLAUDE.md"),
|
|
616
|
+
docsMapPath: resolve2(root, ".massu/docs-map.json"),
|
|
617
|
+
helpSitePath: resolve2(root, "../" + config.project.name + "-help"),
|
|
618
|
+
memoryDbPath: resolve2(root, ".massu/memory.db"),
|
|
619
|
+
knowledgeDbPath: resolve2(root, ".massu/knowledge.db"),
|
|
620
|
+
plansDir: resolve2(root, "docs/plans"),
|
|
621
|
+
docsDir: resolve2(root, "docs"),
|
|
622
|
+
claudeDir: resolve2(root, claudeDirName),
|
|
623
|
+
memoryDir: resolve2(homedir2(), claudeDirName, "projects", encodeMemoryDirName(root), "memory"),
|
|
624
|
+
sessionStatePath: resolve2(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
|
|
625
|
+
sessionArchivePath: resolve2(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
|
|
626
|
+
mcpJsonPath: resolve2(root, ".mcp.json"),
|
|
627
|
+
settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
|
|
559
628
|
};
|
|
560
629
|
}
|
|
561
630
|
|
|
@@ -563,8 +632,8 @@ function getResolvedPaths() {
|
|
|
563
632
|
function getMemoryDb() {
|
|
564
633
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
565
634
|
const dir = dirname2(dbPath);
|
|
566
|
-
if (!
|
|
567
|
-
|
|
635
|
+
if (!existsSync3(dir)) {
|
|
636
|
+
mkdirSync2(dir, { recursive: true });
|
|
568
637
|
}
|
|
569
638
|
const db = new Database(dbPath);
|
|
570
639
|
db.pragma("journal_mode = WAL");
|
|
@@ -1392,14 +1461,14 @@ function safeParseJson(json, fallback) {
|
|
|
1392
1461
|
}
|
|
1393
1462
|
}
|
|
1394
1463
|
function readStdin() {
|
|
1395
|
-
return new Promise((
|
|
1464
|
+
return new Promise((resolve4) => {
|
|
1396
1465
|
let data = "";
|
|
1397
1466
|
process.stdin.setEncoding("utf-8");
|
|
1398
1467
|
process.stdin.on("data", (chunk) => {
|
|
1399
1468
|
data += chunk;
|
|
1400
1469
|
});
|
|
1401
|
-
process.stdin.on("end", () =>
|
|
1402
|
-
setTimeout(() =>
|
|
1470
|
+
process.stdin.on("end", () => resolve4(data));
|
|
1471
|
+
setTimeout(() => resolve4(data), 3e3);
|
|
1403
1472
|
});
|
|
1404
1473
|
}
|
|
1405
1474
|
main();
|