@massu/core 1.15.1 → 1.15.3

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.
@@ -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 existsSync2, mkdirSync } from "fs";
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 (existsSync(resolve(dir, "massu.config.yaml"))) {
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 (existsSync(resolve(dir, "package.json"))) {
480
+ if (existsSync2(resolve2(dir, "package.json"))) {
418
481
  return dir;
419
482
  }
420
- if (existsSync(resolve(dir, ".git"))) {
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 = resolve(root, "massu.config.yaml");
501
+ const configPath = resolve2(root, "massu.config.yaml");
439
502
  let rawYaml = {};
440
- if (existsSync(configPath)) {
441
- const content = readFileSync(configPath, "utf-8");
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 : resolve(root, parsed.project.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
- if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
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: process.env.MASSU_API_KEY
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: resolve(root, ".codegraph/codegraph.db"),
532
- dataDbPath: resolve(root, ".massu/data.db"),
533
- prismaSchemaPath: resolve(root, config.paths.schema ?? "prisma/schema.prisma"),
534
- rootRouterPath: resolve(root, config.paths.routerRoot ?? "src/server/api/root.ts"),
535
- routersDir: resolve(root, config.paths.routers ?? "src/server/api/routers"),
536
- srcDir: resolve(root, config.paths.source),
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
- resolve(root, target)
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: resolve(root, claudeDirName, "patterns"),
546
- claudeMdPath: resolve(root, claudeDirName, "CLAUDE.md"),
547
- docsMapPath: resolve(root, ".massu/docs-map.json"),
548
- helpSitePath: resolve(root, "../" + config.project.name + "-help"),
549
- memoryDbPath: resolve(root, ".massu/memory.db"),
550
- knowledgeDbPath: resolve(root, ".massu/knowledge.db"),
551
- plansDir: resolve(root, "docs/plans"),
552
- docsDir: resolve(root, "docs"),
553
- claudeDir: resolve(root, claudeDirName),
554
- memoryDir: resolve(homedir(), claudeDirName, "projects", encodeMemoryDirName(root), "memory"),
555
- sessionStatePath: resolve(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
556
- sessionArchivePath: resolve(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
557
- mcpJsonPath: resolve(root, ".mcp.json"),
558
- settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
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 (!existsSync2(dir)) {
567
- mkdirSync(dir, { recursive: true });
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");
@@ -1223,14 +1292,14 @@ async function main() {
1223
1292
  process.exit(0);
1224
1293
  }
1225
1294
  function readStdin() {
1226
- return new Promise((resolve3) => {
1295
+ return new Promise((resolve4) => {
1227
1296
  let data = "";
1228
1297
  process.stdin.setEncoding("utf-8");
1229
1298
  process.stdin.on("data", (chunk) => {
1230
1299
  data += chunk;
1231
1300
  });
1232
- process.stdin.on("end", () => resolve3(data));
1233
- setTimeout(() => resolve3(data), 3e3);
1301
+ process.stdin.on("end", () => resolve4(data));
1302
+ setTimeout(() => resolve4(data), 3e3);
1234
1303
  });
1235
1304
  }
1236
1305
  main();
@@ -9,15 +9,79 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
 
10
10
  // src/hooks/fix-detector.ts
11
11
  import { execFileSync } from "child_process";
12
- import { existsSync as existsSync2, appendFileSync, mkdirSync, readFileSync as readFileSync2 } from "fs";
12
+ import { existsSync as existsSync3, appendFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
13
13
  import { tmpdir } from "os";
14
14
  import { join } from "path";
15
15
 
16
16
  // src/config.ts
17
- import { resolve, dirname } from "path";
18
- import { existsSync, readFileSync } from "fs";
17
+ import { resolve as resolve2, dirname } from "path";
18
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
19
19
  import { parse as parseYaml } from "yaml";
20
20
  import { z } from "zod";
21
+
22
+ // src/credentials.ts
23
+ import { homedir } from "os";
24
+ import { resolve } from "path";
25
+ import {
26
+ existsSync,
27
+ readFileSync,
28
+ writeFileSync,
29
+ mkdirSync,
30
+ rmSync,
31
+ chmodSync
32
+ } from "fs";
33
+ var MASSU_ENV_API_KEY = "MASSU_API_KEY";
34
+ var MASSU_ENV_CLOUD_ENDPOINT = "MASSU_CLOUD_ENDPOINT";
35
+ var DEFAULT_CLOUD_ENDPOINT = "https://api.massu.ai/v1";
36
+ function credentialsDir(home = homedir()) {
37
+ return resolve(home, ".massu");
38
+ }
39
+ function credentialsPath(home = homedir()) {
40
+ return resolve(credentialsDir(home), "credentials");
41
+ }
42
+ function readUserCredentials(home = homedir()) {
43
+ try {
44
+ const p = credentialsPath(home);
45
+ if (!existsSync(p)) return void 0;
46
+ const parsed = JSON.parse(readFileSync(p, "utf-8"));
47
+ const key = typeof parsed?.apiKey === "string" ? parsed.apiKey.trim() : "";
48
+ return key.length > 0 ? key : void 0;
49
+ } catch {
50
+ return void 0;
51
+ }
52
+ }
53
+ function isUnresolvedLiteral(v) {
54
+ return /^\$\{[^}]+\}$/.test(v);
55
+ }
56
+ function resolveApiKey(opts = {}) {
57
+ const env = opts.env ?? process.env;
58
+ const home = opts.home ?? homedir();
59
+ const cfg = typeof opts.configApiKey === "string" ? opts.configApiKey.trim() : "";
60
+ if (cfg.length > 0 && !isUnresolvedLiteral(cfg)) {
61
+ return { apiKey: cfg, source: "config" };
62
+ }
63
+ const envRaw = env[MASSU_ENV_API_KEY];
64
+ const envKey = typeof envRaw === "string" ? envRaw.trim() : "";
65
+ if (envKey.length > 0) {
66
+ return { apiKey: envKey, source: "env" };
67
+ }
68
+ const fileKey = readUserCredentials(home);
69
+ if (fileKey) {
70
+ return { apiKey: fileKey, source: "user-file" };
71
+ }
72
+ return { source: "none" };
73
+ }
74
+ function resolveEndpoint(opts = {}) {
75
+ const env = opts.env ?? process.env;
76
+ const cfg = typeof opts.configEndpoint === "string" ? opts.configEndpoint.trim() : "";
77
+ if (cfg.length > 0) return cfg;
78
+ const envRaw = env[MASSU_ENV_CLOUD_ENDPOINT];
79
+ const envEp = typeof envRaw === "string" ? envRaw.trim() : "";
80
+ if (envEp.length > 0) return envEp;
81
+ return DEFAULT_CLOUD_ENDPOINT;
82
+ }
83
+
84
+ // src/config.ts
21
85
  var DomainConfigSchema = z.object({
22
86
  name: z.string().default("Unknown"),
23
87
  routers: z.array(z.string()).default([]),
@@ -399,12 +463,13 @@ var RawConfigSchema = z.object({
399
463
  lsp: LSPConfigSchema.optional()
400
464
  }).passthrough();
401
465
  var _config = null;
466
+ var _resolvedApiKeySource = "none";
402
467
  var _projectRoot = null;
403
468
  function findProjectRoot() {
404
469
  const cwd = process.cwd();
405
470
  let dir = cwd;
406
471
  while (true) {
407
- if (existsSync(resolve(dir, "massu.config.yaml"))) {
472
+ if (existsSync2(resolve2(dir, "massu.config.yaml"))) {
408
473
  return dir;
409
474
  }
410
475
  const parent = dirname(dir);
@@ -413,10 +478,10 @@ function findProjectRoot() {
413
478
  }
414
479
  dir = cwd;
415
480
  while (true) {
416
- if (existsSync(resolve(dir, "package.json"))) {
481
+ if (existsSync2(resolve2(dir, "package.json"))) {
417
482
  return dir;
418
483
  }
419
- if (existsSync(resolve(dir, ".git"))) {
484
+ if (existsSync2(resolve2(dir, ".git"))) {
420
485
  return dir;
421
486
  }
422
487
  const parent = dirname(dir);
@@ -434,10 +499,10 @@ function getProjectRoot() {
434
499
  function getConfig() {
435
500
  if (_config) return _config;
436
501
  const root = getProjectRoot();
437
- const configPath = resolve(root, "massu.config.yaml");
502
+ const configPath = resolve2(root, "massu.config.yaml");
438
503
  let rawYaml = {};
439
- if (existsSync(configPath)) {
440
- const content = readFileSync(configPath, "utf-8");
504
+ if (existsSync2(configPath)) {
505
+ const content = readFileSync2(configPath, "utf-8");
441
506
  rawYaml = parseYaml(content) ?? {};
442
507
  }
443
508
  const result = RawConfigSchema.safeParse(rawYaml);
@@ -454,7 +519,7 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
454
519
  );
455
520
  }
456
521
  const parsed = result.data;
457
- const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve(root, parsed.project.root);
522
+ const projectRoot = parsed.project.root === "auto" || !parsed.project.root ? root : resolve2(root, parsed.project.root);
458
523
  const fw = parsed.framework;
459
524
  let router = fw.router;
460
525
  let orm = fw.orm;
@@ -512,13 +577,19 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
512
577
  telemetry: parsed.telemetry,
513
578
  lsp: parsed.lsp
514
579
  };
515
- if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
580
+ const resolvedKey = resolveApiKey({ configApiKey: _config.cloud?.apiKey });
581
+ _resolvedApiKeySource = resolvedKey.source;
582
+ const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
583
+ if (resolvedKey.apiKey) {
516
584
  _config.cloud = {
517
585
  enabled: true,
518
586
  sync: { memory: true, analytics: true, audit: true },
519
587
  ..._config.cloud,
520
- apiKey: process.env.MASSU_API_KEY
588
+ apiKey: resolvedKey.apiKey,
589
+ endpoint: resolvedEndpoint
521
590
  };
591
+ } else if (_config.cloud) {
592
+ _config.cloud = { ..._config.cloud, endpoint: _config.cloud.endpoint ?? resolvedEndpoint };
522
593
  }
523
594
  return _config;
524
595
  }
@@ -572,15 +643,15 @@ var FIX_HEURISTICS = [
572
643
  ];
573
644
  function getSessionFlagPath(sessionId) {
574
645
  const dir = join(tmpdir(), "massu-auto-learning");
575
- if (!existsSync2(dir)) {
576
- mkdirSync(dir, { recursive: true });
646
+ if (!existsSync3(dir)) {
647
+ mkdirSync2(dir, { recursive: true });
577
648
  }
578
649
  return join(dir, `fixes-${sessionId.slice(0, 12)}.jsonl`);
579
650
  }
580
651
  function _stateDir() {
581
652
  const dir = join(tmpdir(), "massu-fix-detector-state");
582
- if (!existsSync2(dir)) {
583
- mkdirSync(dir, { recursive: true });
653
+ if (!existsSync3(dir)) {
654
+ mkdirSync2(dir, { recursive: true });
584
655
  }
585
656
  return dir;
586
657
  }
@@ -594,8 +665,8 @@ function _cwdHash(cwd) {
594
665
  function isGitWorkTreeFast(cwd) {
595
666
  try {
596
667
  const cachePath = join(_stateDir(), `worktree-${_cwdHash(cwd)}.json`);
597
- if (existsSync2(cachePath)) {
598
- const cached = JSON.parse(readFileSync2(cachePath, "utf-8"));
668
+ if (existsSync3(cachePath)) {
669
+ const cached = JSON.parse(readFileSync3(cachePath, "utf-8"));
599
670
  const ageMs = Date.now() - cached.ts;
600
671
  if (ageMs < 36e5) return cached.isWorkTree;
601
672
  }
@@ -622,7 +693,7 @@ function _disabledFlagPath(sessionId) {
622
693
  }
623
694
  function isSessionAutoDisabled(sessionId) {
624
695
  try {
625
- return existsSync2(_disabledFlagPath(sessionId));
696
+ return existsSync3(_disabledFlagPath(sessionId));
626
697
  } catch {
627
698
  return false;
628
699
  }
@@ -640,7 +711,7 @@ async function main() {
640
711
  const input = await readStdin();
641
712
  const hookInput = JSON.parse(input);
642
713
  const filePath = hookInput.tool_input?.file_path;
643
- if (!filePath || !existsSync2(filePath)) {
714
+ if (!filePath || !existsSync3(filePath)) {
644
715
  process.exit(0);
645
716
  return;
646
717
  }
@@ -701,7 +772,7 @@ async function main() {
701
772
  };
702
773
  const flagPath = getSessionFlagPath(hookInput.session_id);
703
774
  appendFileSync(flagPath, JSON.stringify(signal) + "\n");
704
- const lines = readFileSync2(flagPath, "utf-8").split("\n").filter(Boolean);
775
+ const lines = readFileSync3(flagPath, "utf-8").split("\n").filter(Boolean);
705
776
  if (lines.length === 1) {
706
777
  writeHookMessage(
707
778
  `[Massu Auto-Learning] Bug fix detected in ${filePath} (signals: ${detected.join(", ")}). The auto-learning pipeline will prompt you at session end to create an incident report, derive a prevention rule, and add enforcement.`
@@ -712,14 +783,14 @@ async function main() {
712
783
  process.exit(0);
713
784
  }
714
785
  function readStdin() {
715
- return new Promise((resolve2) => {
786
+ return new Promise((resolve3) => {
716
787
  let data = "";
717
788
  process.stdin.setEncoding("utf-8");
718
789
  process.stdin.on("data", (chunk) => {
719
790
  data += chunk;
720
791
  });
721
- process.stdin.on("end", () => resolve2(data));
722
- setTimeout(() => resolve2(data), 3e3);
792
+ process.stdin.on("end", () => resolve3(data));
793
+ setTimeout(() => resolve3(data), 3e3);
723
794
  });
724
795
  }
725
796
  main();