@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
|
@@ -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");
|
|
@@ -1404,8 +1473,8 @@ function setLastProcessedLine(db, sessionId, lineNumber) {
|
|
|
1404
1473
|
}
|
|
1405
1474
|
|
|
1406
1475
|
// src/session-archiver.ts
|
|
1407
|
-
import { existsSync as
|
|
1408
|
-
import { resolve as
|
|
1476
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync } from "fs";
|
|
1477
|
+
import { resolve as resolve4, dirname as dirname3 } from "path";
|
|
1409
1478
|
|
|
1410
1479
|
// src/session-state-generator.ts
|
|
1411
1480
|
function generateCurrentMd(db, sessionId) {
|
|
@@ -1556,29 +1625,29 @@ function archiveAndRegenerate(db, sessionId) {
|
|
|
1556
1625
|
const archiveDir = resolved.sessionArchivePath;
|
|
1557
1626
|
let archived = false;
|
|
1558
1627
|
let archivePath;
|
|
1559
|
-
if (
|
|
1560
|
-
const existingContent =
|
|
1628
|
+
if (existsSync4(currentMdPath)) {
|
|
1629
|
+
const existingContent = readFileSync3(currentMdPath, "utf-8");
|
|
1561
1630
|
if (existingContent.trim().length > 10) {
|
|
1562
1631
|
const { date, slug } = extractArchiveInfo(existingContent);
|
|
1563
|
-
archivePath =
|
|
1564
|
-
if (!
|
|
1565
|
-
|
|
1632
|
+
archivePath = resolve4(archiveDir, `${date}-${slug}.md`);
|
|
1633
|
+
if (!existsSync4(archiveDir)) {
|
|
1634
|
+
mkdirSync3(archiveDir, { recursive: true });
|
|
1566
1635
|
}
|
|
1567
1636
|
try {
|
|
1568
1637
|
renameSync(currentMdPath, archivePath);
|
|
1569
1638
|
archived = true;
|
|
1570
1639
|
} catch (_e) {
|
|
1571
|
-
|
|
1640
|
+
writeFileSync2(archivePath, existingContent);
|
|
1572
1641
|
archived = true;
|
|
1573
1642
|
}
|
|
1574
1643
|
}
|
|
1575
1644
|
}
|
|
1576
1645
|
const newContent = generateCurrentMd(db, sessionId);
|
|
1577
1646
|
const dir = dirname3(currentMdPath);
|
|
1578
|
-
if (!
|
|
1579
|
-
|
|
1647
|
+
if (!existsSync4(dir)) {
|
|
1648
|
+
mkdirSync3(dir, { recursive: true });
|
|
1580
1649
|
}
|
|
1581
|
-
|
|
1650
|
+
writeFileSync2(currentMdPath, newContent, "utf-8");
|
|
1582
1651
|
return { archived, archivePath, newContent };
|
|
1583
1652
|
}
|
|
1584
1653
|
function extractArchiveInfo(content) {
|
|
@@ -1794,7 +1863,7 @@ async function syncToCloud(db, payload) {
|
|
|
1794
1863
|
const requestTimeoutMs = cloud.requestTimeoutMs ?? DEFAULT_CLOUD_REQUEST_TIMEOUT_MS;
|
|
1795
1864
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
1796
1865
|
try {
|
|
1797
|
-
const response = await fetch(endpoint
|
|
1866
|
+
const response = await fetch(`${endpoint}/sync`, {
|
|
1798
1867
|
method: "POST",
|
|
1799
1868
|
headers: {
|
|
1800
1869
|
"Content-Type": "application/json",
|
|
@@ -1867,11 +1936,11 @@ async function drainSyncQueue(db) {
|
|
|
1867
1936
|
}
|
|
1868
1937
|
}
|
|
1869
1938
|
function sleep(ms) {
|
|
1870
|
-
return new Promise((
|
|
1939
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
1871
1940
|
}
|
|
1872
1941
|
|
|
1873
1942
|
// src/team-rule-sync.ts
|
|
1874
|
-
import { existsSync as
|
|
1943
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync4 } from "fs";
|
|
1875
1944
|
import { join, dirname as dirname4 } from "path";
|
|
1876
1945
|
|
|
1877
1946
|
// src/license.ts
|
|
@@ -2217,7 +2286,7 @@ async function pullTeamPromotions(db, opts = {}) {
|
|
|
2217
2286
|
result.revoked_handled += 1;
|
|
2218
2287
|
continue;
|
|
2219
2288
|
}
|
|
2220
|
-
if (
|
|
2289
|
+
if (existsSync5(candidatePath) || alreadyApplied(db, p.prompt_hash)) {
|
|
2221
2290
|
result.skipped += 1;
|
|
2222
2291
|
continue;
|
|
2223
2292
|
}
|
|
@@ -2267,7 +2336,7 @@ function alreadyApplied(db, promptHash) {
|
|
|
2267
2336
|
}
|
|
2268
2337
|
}
|
|
2269
2338
|
function handleRevocation(db, projectRoot, candidatePath, promptHash) {
|
|
2270
|
-
if (
|
|
2339
|
+
if (existsSync5(candidatePath)) {
|
|
2271
2340
|
try {
|
|
2272
2341
|
unlinkSync(candidatePath);
|
|
2273
2342
|
} catch {
|
|
@@ -2316,8 +2385,8 @@ function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
|
|
|
2316
2385
|
...p.review_attestation !== void 0 ? { publisher_review_attestation: p.review_attestation } : {}
|
|
2317
2386
|
};
|
|
2318
2387
|
const dir = dirname4(candidatePath);
|
|
2319
|
-
if (!
|
|
2320
|
-
|
|
2388
|
+
if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
|
|
2389
|
+
writeFileSync3(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
|
|
2321
2390
|
try {
|
|
2322
2391
|
shareObservation(db, p.promoted_by, getProjectName(), "rule_promotion", promptText, {
|
|
2323
2392
|
filePath: void 0,
|
|
@@ -2921,14 +2990,14 @@ function extractFilesFromToolCall(toolName, input) {
|
|
|
2921
2990
|
return [];
|
|
2922
2991
|
}
|
|
2923
2992
|
function readStdin() {
|
|
2924
|
-
return new Promise((
|
|
2993
|
+
return new Promise((resolve5) => {
|
|
2925
2994
|
let data = "";
|
|
2926
2995
|
process.stdin.setEncoding("utf-8");
|
|
2927
2996
|
process.stdin.on("data", (chunk) => {
|
|
2928
2997
|
data += chunk;
|
|
2929
2998
|
});
|
|
2930
|
-
process.stdin.on("end", () =>
|
|
2931
|
-
setTimeout(() =>
|
|
2999
|
+
process.stdin.on("end", () => resolve5(data));
|
|
3000
|
+
setTimeout(() => resolve5(data), 5e3);
|
|
2932
3001
|
});
|
|
2933
3002
|
}
|
|
2934
3003
|
main();
|