@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.
- package/CHANGELOG.md +25 -0
- package/dist/cli.js +977 -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
|
@@ -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");
|
|
@@ -1289,7 +1358,7 @@ function linkSessionToTask(db, sessionId, taskId) {
|
|
|
1289
1358
|
}
|
|
1290
1359
|
|
|
1291
1360
|
// src/hooks/user-prompt.ts
|
|
1292
|
-
import { existsSync as
|
|
1361
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync3, readdirSync, openSync, fstatSync, readSync, closeSync } from "fs";
|
|
1293
1362
|
import { join } from "path";
|
|
1294
1363
|
|
|
1295
1364
|
// src/rule-candidate-detector.ts
|
|
@@ -1625,7 +1694,7 @@ async function main() {
|
|
|
1625
1694
|
const fileRefs = extractFileReferences(prompt);
|
|
1626
1695
|
if (fileRefs.length > 0) {
|
|
1627
1696
|
const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
|
|
1628
|
-
if (knowledgeDbPath &&
|
|
1697
|
+
if (knowledgeDbPath && existsSync4(knowledgeDbPath)) {
|
|
1629
1698
|
const BetterSqlite3Ctor = (await import("better-sqlite3")).default;
|
|
1630
1699
|
const kdb = new BetterSqlite3Ctor(knowledgeDbPath, { readonly: true });
|
|
1631
1700
|
try {
|
|
@@ -1683,11 +1752,11 @@ async function main() {
|
|
|
1683
1752
|
if (scoreResult.emitCandidate) {
|
|
1684
1753
|
const cachedTier = getCachedTierReadOnly(db);
|
|
1685
1754
|
if (entitledForAutoLearning(cachedTier)) {
|
|
1686
|
-
|
|
1755
|
+
mkdirSync3(candidateDir, { recursive: true });
|
|
1687
1756
|
const promptHash = hashPrompt(prompt);
|
|
1688
1757
|
const candidatePath = join(candidateDir, `${promptHash}.json`);
|
|
1689
|
-
if (!
|
|
1690
|
-
|
|
1758
|
+
if (!existsSync4(candidatePath)) {
|
|
1759
|
+
writeFileSync2(candidatePath, JSON.stringify({
|
|
1691
1760
|
prompt,
|
|
1692
1761
|
prompt_hash: promptHash,
|
|
1693
1762
|
score: scoreResult.score,
|
|
@@ -1711,27 +1780,27 @@ async function main() {
|
|
|
1711
1780
|
}
|
|
1712
1781
|
} else {
|
|
1713
1782
|
const nudgePath = join(candidateDir, ".last-tier-nudge");
|
|
1714
|
-
if (!
|
|
1715
|
-
|
|
1783
|
+
if (!existsSync4(nudgePath)) {
|
|
1784
|
+
mkdirSync3(candidateDir, { recursive: true });
|
|
1716
1785
|
process.stderr.write(
|
|
1717
1786
|
`
|
|
1718
1787
|
[RULE CANDIDATE] ${autoLearningUpgradeMessage(cachedTier)}
|
|
1719
1788
|
|
|
1720
1789
|
`
|
|
1721
1790
|
);
|
|
1722
|
-
|
|
1791
|
+
writeFileSync2(nudgePath, (/* @__PURE__ */ new Date()).toISOString());
|
|
1723
1792
|
}
|
|
1724
1793
|
}
|
|
1725
1794
|
}
|
|
1726
|
-
if (
|
|
1795
|
+
if (existsSync4(candidateDir)) {
|
|
1727
1796
|
const candidates = readdirSync(candidateDir).filter(
|
|
1728
1797
|
(f) => f.endsWith(".json") && !f.startsWith(".")
|
|
1729
1798
|
);
|
|
1730
1799
|
const candidateCount = candidates.length;
|
|
1731
1800
|
const surfacedPath = join(candidateDir, ".last-surfaced");
|
|
1732
1801
|
let lastSurfaced = 0;
|
|
1733
|
-
if (
|
|
1734
|
-
const raw =
|
|
1802
|
+
if (existsSync4(surfacedPath)) {
|
|
1803
|
+
const raw = readFileSync3(surfacedPath, "utf-8").trim();
|
|
1735
1804
|
const parsed = parseInt(raw, 10);
|
|
1736
1805
|
if (!Number.isNaN(parsed)) lastSurfaced = parsed;
|
|
1737
1806
|
}
|
|
@@ -1742,17 +1811,17 @@ async function main() {
|
|
|
1742
1811
|
|
|
1743
1812
|
`
|
|
1744
1813
|
);
|
|
1745
|
-
|
|
1814
|
+
writeFileSync2(surfacedPath, String(candidateCount));
|
|
1746
1815
|
}
|
|
1747
1816
|
}
|
|
1748
1817
|
} catch (candidateErr) {
|
|
1749
1818
|
try {
|
|
1750
1819
|
const dir = join(hookInput.cwd, ".massu", "rule-candidates");
|
|
1751
|
-
if (!
|
|
1820
|
+
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
|
|
1752
1821
|
const logPath = join(dir, ".detector-failures.jsonl");
|
|
1753
|
-
const pre =
|
|
1822
|
+
const pre = existsSync4(logPath) ? readFileSync3(logPath, "utf-8") : "";
|
|
1754
1823
|
const sep = pre && !pre.endsWith("\n") ? "\n" : "";
|
|
1755
|
-
|
|
1824
|
+
writeFileSync2(logPath, pre + sep + JSON.stringify({
|
|
1756
1825
|
session_id,
|
|
1757
1826
|
// Security review (plan-2026-05-27): log the prompt HASH, never a
|
|
1758
1827
|
// raw prompt excerpt — the failure log must not persist PII/secrets.
|
|
@@ -1772,7 +1841,7 @@ async function main() {
|
|
|
1772
1841
|
}
|
|
1773
1842
|
function detectPriorAssistantTurn(transcriptPath) {
|
|
1774
1843
|
try {
|
|
1775
|
-
if (!transcriptPath || !
|
|
1844
|
+
if (!transcriptPath || !existsSync4(transcriptPath)) {
|
|
1776
1845
|
return { hadEditOrWrite: false, files: [] };
|
|
1777
1846
|
}
|
|
1778
1847
|
if (!transcriptPath.includes("/.claude/projects/")) {
|
|
@@ -1860,14 +1929,14 @@ async function getGitBranch() {
|
|
|
1860
1929
|
}
|
|
1861
1930
|
}
|
|
1862
1931
|
function readStdin() {
|
|
1863
|
-
return new Promise((
|
|
1932
|
+
return new Promise((resolve4) => {
|
|
1864
1933
|
let data = "";
|
|
1865
1934
|
process.stdin.setEncoding("utf-8");
|
|
1866
1935
|
process.stdin.on("data", (chunk) => {
|
|
1867
1936
|
data += chunk;
|
|
1868
1937
|
});
|
|
1869
|
-
process.stdin.on("end", () =>
|
|
1870
|
-
setTimeout(() =>
|
|
1938
|
+
process.stdin.on("end", () => resolve4(data));
|
|
1939
|
+
setTimeout(() => resolve4(data), 3e3);
|
|
1871
1940
|
});
|
|
1872
1941
|
}
|
|
1873
1942
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 73 total), 56 workflow commands, 11 agents, 20+ patterns",
|
|
6
6
|
"main": "./dist/cli.js",
|