@massu/core 2.5.0 → 2.5.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 +231 -0
- package/commands/massu-bearings.md +1 -1
- package/commands/massu-recap.md +16 -5
- package/dist/cli.js +716 -258
- package/dist/hooks/auto-learning-pipeline.js +297 -56
- package/dist/hooks/classify-failure.js +323 -25
- package/dist/hooks/cost-tracker.js +47 -10
- package/dist/hooks/fix-detector.js +94 -10
- package/dist/hooks/incident-pipeline.js +77 -10
- package/dist/hooks/intent-suggester.js +77 -10
- package/dist/hooks/memory-recall.js +77 -10
- package/dist/hooks/post-edit-context.js +125 -11
- package/dist/hooks/post-tool-use.js +44 -7
- package/dist/hooks/pre-compact.js +43 -7
- package/dist/hooks/pre-delete-check.js +125 -13
- package/dist/hooks/pre-tool-use-gate.js +135 -17
- package/dist/hooks/quality-event.js +47 -10
- package/dist/hooks/rule-enforcement-pipeline.js +77 -10
- package/dist/hooks/security-gate.js +75 -10
- package/dist/hooks/session-end.js +43 -7
- package/dist/hooks/session-start.js +144 -38
- package/dist/hooks/user-prompt.js +43 -7
- package/package.json +1 -1
|
@@ -1167,6 +1167,13 @@ var init_config = __esm({
|
|
|
1167
1167
|
});
|
|
1168
1168
|
LanguageFrameworkEntrySchema = z2.object({
|
|
1169
1169
|
framework: z2.string().optional(),
|
|
1170
|
+
// `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
|
|
1171
|
+
// by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
|
|
1172
|
+
// `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
|
|
1173
|
+
// derives the index builders' candidate set from it, so it is typed here rather than
|
|
1174
|
+
// read as an unknown: a consumer that has to shape-check its own SoT is one branch away
|
|
1175
|
+
// from silently contributing `undefined` to a path predicate.
|
|
1176
|
+
source_dirs: z2.array(z2.string()).optional(),
|
|
1170
1177
|
test_framework: z2.string().optional(),
|
|
1171
1178
|
test: z2.string().optional(),
|
|
1172
1179
|
runtime: z2.string().optional(),
|
|
@@ -1339,6 +1346,16 @@ var init_memory_vector = __esm({
|
|
|
1339
1346
|
}
|
|
1340
1347
|
});
|
|
1341
1348
|
|
|
1349
|
+
// src/lib/timestamps.ts
|
|
1350
|
+
function nowIso() {
|
|
1351
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1352
|
+
}
|
|
1353
|
+
var init_timestamps = __esm({
|
|
1354
|
+
"src/lib/timestamps.ts"() {
|
|
1355
|
+
"use strict";
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1342
1359
|
// src/db-backup.ts
|
|
1343
1360
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1344
1361
|
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
@@ -3599,8 +3616,8 @@ function linkSessionToTask(db, sessionId, taskId) {
|
|
|
3599
3616
|
}
|
|
3600
3617
|
function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
|
|
3601
3618
|
const result = db.prepare(`
|
|
3602
|
-
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
|
|
3603
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3619
|
+
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
|
|
3620
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3604
3621
|
`).run(
|
|
3605
3622
|
sessionId,
|
|
3606
3623
|
turnNumber,
|
|
@@ -3609,14 +3626,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
|
|
|
3609
3626
|
toolCallsJson,
|
|
3610
3627
|
toolCallCount,
|
|
3611
3628
|
promptTokens,
|
|
3612
|
-
responseTokens
|
|
3629
|
+
responseTokens,
|
|
3630
|
+
nowIso()
|
|
3613
3631
|
);
|
|
3614
3632
|
return Number(result.lastInsertRowid);
|
|
3615
3633
|
}
|
|
3616
3634
|
function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
|
|
3617
3635
|
db.prepare(`
|
|
3618
|
-
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
|
|
3619
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3636
|
+
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved, created_at)
|
|
3637
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3620
3638
|
`).run(
|
|
3621
3639
|
sessionId,
|
|
3622
3640
|
turnNumber,
|
|
@@ -3625,7 +3643,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
|
|
|
3625
3643
|
inputSize,
|
|
3626
3644
|
outputSize,
|
|
3627
3645
|
success ? 1 : 0,
|
|
3628
|
-
filesInvolved ? JSON.stringify(filesInvolved) : null
|
|
3646
|
+
filesInvolved ? JSON.stringify(filesInvolved) : null,
|
|
3647
|
+
nowIso()
|
|
3629
3648
|
);
|
|
3630
3649
|
}
|
|
3631
3650
|
function getLastProcessedLine(db, sessionId) {
|
|
@@ -3884,6 +3903,7 @@ var init_memory_db = __esm({
|
|
|
3884
3903
|
init_db_driver();
|
|
3885
3904
|
init_config();
|
|
3886
3905
|
init_memory_vector();
|
|
3906
|
+
init_timestamps();
|
|
3887
3907
|
init_db_backup();
|
|
3888
3908
|
init_rule_delivery();
|
|
3889
3909
|
init_rule_candidate_store();
|
|
@@ -4003,6 +4023,53 @@ function t(suffix) {
|
|
|
4003
4023
|
return `${getConfig().toolPrefix}_${suffix}`;
|
|
4004
4024
|
}
|
|
4005
4025
|
|
|
4026
|
+
// src/lib/source-layout.ts
|
|
4027
|
+
init_config();
|
|
4028
|
+
function normalizeDir(raw) {
|
|
4029
|
+
if (typeof raw !== "string") return null;
|
|
4030
|
+
const posix = raw.replace(/\\/g, "/");
|
|
4031
|
+
if (posix.startsWith("/") || /^[A-Za-z]:\//.test(posix)) return null;
|
|
4032
|
+
const parts = posix.split("/").filter((p) => p !== "" && p !== ".");
|
|
4033
|
+
if (parts.some((p) => p === "..")) return null;
|
|
4034
|
+
if (parts.length === 0) return ".";
|
|
4035
|
+
return parts.join("/");
|
|
4036
|
+
}
|
|
4037
|
+
function declaredLanguageSourceDirs() {
|
|
4038
|
+
const languages = getConfig().framework.languages;
|
|
4039
|
+
if (!languages) return [];
|
|
4040
|
+
const out = [];
|
|
4041
|
+
for (const entry of Object.values(languages)) {
|
|
4042
|
+
if (Array.isArray(entry.source_dirs)) out.push(...entry.source_dirs);
|
|
4043
|
+
}
|
|
4044
|
+
return out;
|
|
4045
|
+
}
|
|
4046
|
+
function getSourceLayout() {
|
|
4047
|
+
const config = getConfig();
|
|
4048
|
+
const paths = config.paths;
|
|
4049
|
+
const declared = [
|
|
4050
|
+
paths.source,
|
|
4051
|
+
...paths.monorepo_roots ?? [],
|
|
4052
|
+
...declaredLanguageSourceDirs()
|
|
4053
|
+
];
|
|
4054
|
+
const normalized = declared.map(normalizeDir).filter((d) => d !== null);
|
|
4055
|
+
const includesRoot = normalized.includes(".");
|
|
4056
|
+
const sourceDirs = includesRoot ? [] : [...new Set(normalized)].sort();
|
|
4057
|
+
const source = normalizeDir(paths.source) ?? ".";
|
|
4058
|
+
const under = (child) => source === "." ? child : `${source}/${child}`;
|
|
4059
|
+
return {
|
|
4060
|
+
sourceDirs,
|
|
4061
|
+
includesRoot,
|
|
4062
|
+
extensions: getResolvedPaths().extensions,
|
|
4063
|
+
pagesDir: normalizeDir(paths.pages ?? under("app")) ?? under("app"),
|
|
4064
|
+
componentsDir: normalizeDir(paths.components ?? under("components")) ?? under("components")
|
|
4065
|
+
};
|
|
4066
|
+
}
|
|
4067
|
+
function isUnderSourceDir(path, layout = getSourceLayout()) {
|
|
4068
|
+
if (normalizeDir(path) === null) return false;
|
|
4069
|
+
if (layout.includesRoot) return true;
|
|
4070
|
+
return layout.sourceDirs.some((d) => path.startsWith(`${d}/`));
|
|
4071
|
+
}
|
|
4072
|
+
|
|
4006
4073
|
// src/middleware-tree.ts
|
|
4007
4074
|
function isInMiddlewareTree(dataDb, file) {
|
|
4008
4075
|
const result = dataDb.prepare(`SELECT 1 FROM ${t("middleware_tree")} WHERE file = ?`).get(file);
|
|
@@ -4013,12 +4080,57 @@ function isInMiddlewareTree(dataDb, file) {
|
|
|
4013
4080
|
init_config();
|
|
4014
4081
|
|
|
4015
4082
|
// src/hooks/lib/write-hook-message.ts
|
|
4016
|
-
|
|
4017
|
-
|
|
4083
|
+
var HOOK_EVENTS = [
|
|
4084
|
+
"UserPromptSubmit",
|
|
4085
|
+
"UserPromptExpansion",
|
|
4086
|
+
"SessionStart",
|
|
4087
|
+
"PreToolUse",
|
|
4088
|
+
"PostToolUse",
|
|
4089
|
+
"Stop",
|
|
4090
|
+
"SubagentStop",
|
|
4091
|
+
"PreCompact"
|
|
4092
|
+
];
|
|
4093
|
+
var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
|
|
4094
|
+
function isHookEvent(value) {
|
|
4095
|
+
return typeof value === "string" && HOOK_EVENTS.includes(value);
|
|
4096
|
+
}
|
|
4097
|
+
function writeHookContext(hookEventName, message) {
|
|
4098
|
+
if (!isHookEvent(hookEventName)) {
|
|
4099
|
+
process.stderr.write(
|
|
4100
|
+
`[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
|
|
4101
|
+
`
|
|
4102
|
+
);
|
|
4103
|
+
return;
|
|
4104
|
+
}
|
|
4105
|
+
if (typeof message !== "string" || message.length === 0) return;
|
|
4106
|
+
let additionalContext = message;
|
|
4107
|
+
if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
|
|
4108
|
+
const marker = `
|
|
4109
|
+
[massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
|
|
4110
|
+
additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
|
|
4111
|
+
}
|
|
4112
|
+
process.stdout.write(
|
|
4113
|
+
JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
|
|
4114
|
+
);
|
|
4018
4115
|
}
|
|
4019
4116
|
|
|
4020
4117
|
// src/hooks/post-edit-context.ts
|
|
4021
4118
|
init_hook_failure_signal();
|
|
4119
|
+
|
|
4120
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
4121
|
+
import { pathToFileURL } from "url";
|
|
4122
|
+
function isDirectInvocation(moduleUrl) {
|
|
4123
|
+
const entry = process.argv[1];
|
|
4124
|
+
if (!entry) return false;
|
|
4125
|
+
try {
|
|
4126
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
4127
|
+
} catch {
|
|
4128
|
+
return false;
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
|
|
4132
|
+
// src/hooks/post-edit-context.ts
|
|
4133
|
+
var HOOK_EVENT = "PostToolUse";
|
|
4022
4134
|
async function main() {
|
|
4023
4135
|
try {
|
|
4024
4136
|
const input = await readStdin();
|
|
@@ -4030,7 +4142,7 @@ async function main() {
|
|
|
4030
4142
|
}
|
|
4031
4143
|
const root = getProjectRoot();
|
|
4032
4144
|
const rel = filePath.startsWith(root + "/") ? filePath.slice(root.length + 1) : filePath;
|
|
4033
|
-
if (!rel
|
|
4145
|
+
if (!isUnderSourceDir(rel) && !rel.endsWith(".py")) {
|
|
4034
4146
|
process.exit(0);
|
|
4035
4147
|
return;
|
|
4036
4148
|
}
|
|
@@ -4055,7 +4167,7 @@ async function main() {
|
|
|
4055
4167
|
} catch (_e) {
|
|
4056
4168
|
}
|
|
4057
4169
|
if (warnings.length > 0) {
|
|
4058
|
-
|
|
4170
|
+
writeHookContext(HOOK_EVENT, `[Massu] ${warnings.join(" | ")}`);
|
|
4059
4171
|
}
|
|
4060
4172
|
} catch (err) {
|
|
4061
4173
|
recordHookFailure("post-edit-context", err);
|
|
@@ -4073,4 +4185,6 @@ function readStdin() {
|
|
|
4073
4185
|
setTimeout(() => resolve5(data), 3e3);
|
|
4074
4186
|
});
|
|
4075
4187
|
}
|
|
4076
|
-
|
|
4188
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
4189
|
+
main();
|
|
4190
|
+
}
|
|
@@ -1173,6 +1173,13 @@ var init_config = __esm({
|
|
|
1173
1173
|
});
|
|
1174
1174
|
LanguageFrameworkEntrySchema = z2.object({
|
|
1175
1175
|
framework: z2.string().optional(),
|
|
1176
|
+
// `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
|
|
1177
|
+
// by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
|
|
1178
|
+
// `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
|
|
1179
|
+
// derives the index builders' candidate set from it, so it is typed here rather than
|
|
1180
|
+
// read as an unknown: a consumer that has to shape-check its own SoT is one branch away
|
|
1181
|
+
// from silently contributing `undefined` to a path predicate.
|
|
1182
|
+
source_dirs: z2.array(z2.string()).optional(),
|
|
1176
1183
|
test_framework: z2.string().optional(),
|
|
1177
1184
|
test: z2.string().optional(),
|
|
1178
1185
|
runtime: z2.string().optional(),
|
|
@@ -1345,6 +1352,16 @@ var init_memory_vector = __esm({
|
|
|
1345
1352
|
}
|
|
1346
1353
|
});
|
|
1347
1354
|
|
|
1355
|
+
// src/lib/timestamps.ts
|
|
1356
|
+
function nowIso() {
|
|
1357
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1358
|
+
}
|
|
1359
|
+
var init_timestamps = __esm({
|
|
1360
|
+
"src/lib/timestamps.ts"() {
|
|
1361
|
+
"use strict";
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1348
1365
|
// src/db-backup.ts
|
|
1349
1366
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1350
1367
|
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
@@ -3676,8 +3693,8 @@ function linkSessionToTask(db, sessionId, taskId) {
|
|
|
3676
3693
|
}
|
|
3677
3694
|
function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
|
|
3678
3695
|
const result = db.prepare(`
|
|
3679
|
-
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
|
|
3680
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3696
|
+
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
|
|
3697
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3681
3698
|
`).run(
|
|
3682
3699
|
sessionId,
|
|
3683
3700
|
turnNumber,
|
|
@@ -3686,14 +3703,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
|
|
|
3686
3703
|
toolCallsJson,
|
|
3687
3704
|
toolCallCount,
|
|
3688
3705
|
promptTokens,
|
|
3689
|
-
responseTokens
|
|
3706
|
+
responseTokens,
|
|
3707
|
+
nowIso()
|
|
3690
3708
|
);
|
|
3691
3709
|
return Number(result.lastInsertRowid);
|
|
3692
3710
|
}
|
|
3693
3711
|
function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
|
|
3694
3712
|
db.prepare(`
|
|
3695
|
-
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
|
|
3696
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3713
|
+
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved, created_at)
|
|
3714
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3697
3715
|
`).run(
|
|
3698
3716
|
sessionId,
|
|
3699
3717
|
turnNumber,
|
|
@@ -3702,7 +3720,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
|
|
|
3702
3720
|
inputSize,
|
|
3703
3721
|
outputSize,
|
|
3704
3722
|
success ? 1 : 0,
|
|
3705
|
-
filesInvolved ? JSON.stringify(filesInvolved) : null
|
|
3723
|
+
filesInvolved ? JSON.stringify(filesInvolved) : null,
|
|
3724
|
+
nowIso()
|
|
3706
3725
|
);
|
|
3707
3726
|
}
|
|
3708
3727
|
function getLastProcessedLine(db, sessionId) {
|
|
@@ -3961,6 +3980,7 @@ var init_memory_db = __esm({
|
|
|
3961
3980
|
init_db_driver();
|
|
3962
3981
|
init_config();
|
|
3963
3982
|
init_memory_vector();
|
|
3983
|
+
init_timestamps();
|
|
3964
3984
|
init_db_backup();
|
|
3965
3985
|
init_rule_delivery();
|
|
3966
3986
|
init_rule_candidate_store();
|
|
@@ -4458,6 +4478,9 @@ function enforceSeverityFloors(configWeights, defaults) {
|
|
|
4458
4478
|
// src/lib/sql-table-names.ts
|
|
4459
4479
|
init_config();
|
|
4460
4480
|
|
|
4481
|
+
// src/lib/source-layout.ts
|
|
4482
|
+
init_config();
|
|
4483
|
+
|
|
4461
4484
|
// src/import-resolver.ts
|
|
4462
4485
|
function resolveImportPath(specifier, fromFile) {
|
|
4463
4486
|
if (!specifier.startsWith(".") && !specifier.startsWith("@/")) {
|
|
@@ -5033,6 +5056,18 @@ function mapMemoryTypeToObservationType(memoryType) {
|
|
|
5033
5056
|
}
|
|
5034
5057
|
}
|
|
5035
5058
|
|
|
5059
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
5060
|
+
import { pathToFileURL } from "url";
|
|
5061
|
+
function isDirectInvocation(moduleUrl) {
|
|
5062
|
+
const entry = process.argv[1];
|
|
5063
|
+
if (!entry) return false;
|
|
5064
|
+
try {
|
|
5065
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
5066
|
+
} catch {
|
|
5067
|
+
return false;
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
|
|
5036
5071
|
// src/hooks/post-tool-use.ts
|
|
5037
5072
|
var _yamlParser = null;
|
|
5038
5073
|
var parseYaml3 = (content) => {
|
|
@@ -5371,4 +5406,6 @@ function checkMemoryFileIntegrity(filePath) {
|
|
|
5371
5406
|
}
|
|
5372
5407
|
return issues;
|
|
5373
5408
|
}
|
|
5374
|
-
|
|
5409
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
5410
|
+
main();
|
|
5411
|
+
}
|
|
@@ -1167,6 +1167,13 @@ var init_config = __esm({
|
|
|
1167
1167
|
});
|
|
1168
1168
|
LanguageFrameworkEntrySchema = z2.object({
|
|
1169
1169
|
framework: z2.string().optional(),
|
|
1170
|
+
// `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
|
|
1171
|
+
// by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
|
|
1172
|
+
// `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
|
|
1173
|
+
// derives the index builders' candidate set from it, so it is typed here rather than
|
|
1174
|
+
// read as an unknown: a consumer that has to shape-check its own SoT is one branch away
|
|
1175
|
+
// from silently contributing `undefined` to a path predicate.
|
|
1176
|
+
source_dirs: z2.array(z2.string()).optional(),
|
|
1170
1177
|
test_framework: z2.string().optional(),
|
|
1171
1178
|
test: z2.string().optional(),
|
|
1172
1179
|
runtime: z2.string().optional(),
|
|
@@ -1339,6 +1346,16 @@ var init_memory_vector = __esm({
|
|
|
1339
1346
|
}
|
|
1340
1347
|
});
|
|
1341
1348
|
|
|
1349
|
+
// src/lib/timestamps.ts
|
|
1350
|
+
function nowIso() {
|
|
1351
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1352
|
+
}
|
|
1353
|
+
var init_timestamps = __esm({
|
|
1354
|
+
"src/lib/timestamps.ts"() {
|
|
1355
|
+
"use strict";
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1342
1359
|
// src/db-backup.ts
|
|
1343
1360
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1344
1361
|
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
@@ -3670,8 +3687,8 @@ function linkSessionToTask(db, sessionId, taskId) {
|
|
|
3670
3687
|
}
|
|
3671
3688
|
function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
|
|
3672
3689
|
const result = db.prepare(`
|
|
3673
|
-
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
|
|
3674
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3690
|
+
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
|
|
3691
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3675
3692
|
`).run(
|
|
3676
3693
|
sessionId,
|
|
3677
3694
|
turnNumber,
|
|
@@ -3680,14 +3697,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
|
|
|
3680
3697
|
toolCallsJson,
|
|
3681
3698
|
toolCallCount,
|
|
3682
3699
|
promptTokens,
|
|
3683
|
-
responseTokens
|
|
3700
|
+
responseTokens,
|
|
3701
|
+
nowIso()
|
|
3684
3702
|
);
|
|
3685
3703
|
return Number(result.lastInsertRowid);
|
|
3686
3704
|
}
|
|
3687
3705
|
function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
|
|
3688
3706
|
db.prepare(`
|
|
3689
|
-
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
|
|
3690
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3707
|
+
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved, created_at)
|
|
3708
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3691
3709
|
`).run(
|
|
3692
3710
|
sessionId,
|
|
3693
3711
|
turnNumber,
|
|
@@ -3696,7 +3714,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
|
|
|
3696
3714
|
inputSize,
|
|
3697
3715
|
outputSize,
|
|
3698
3716
|
success ? 1 : 0,
|
|
3699
|
-
filesInvolved ? JSON.stringify(filesInvolved) : null
|
|
3717
|
+
filesInvolved ? JSON.stringify(filesInvolved) : null,
|
|
3718
|
+
nowIso()
|
|
3700
3719
|
);
|
|
3701
3720
|
}
|
|
3702
3721
|
function getLastProcessedLine(db, sessionId) {
|
|
@@ -3955,6 +3974,7 @@ var init_memory_db = __esm({
|
|
|
3955
3974
|
init_db_driver();
|
|
3956
3975
|
init_config();
|
|
3957
3976
|
init_memory_vector();
|
|
3977
|
+
init_timestamps();
|
|
3958
3978
|
init_db_backup();
|
|
3959
3979
|
init_rule_delivery();
|
|
3960
3980
|
init_rule_candidate_store();
|
|
@@ -3997,6 +4017,20 @@ function logAuditEntry(db, entry) {
|
|
|
3997
4017
|
|
|
3998
4018
|
// src/hooks/pre-compact.ts
|
|
3999
4019
|
init_hook_failure_signal();
|
|
4020
|
+
|
|
4021
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
4022
|
+
import { pathToFileURL } from "url";
|
|
4023
|
+
function isDirectInvocation(moduleUrl) {
|
|
4024
|
+
const entry = process.argv[1];
|
|
4025
|
+
if (!entry) return false;
|
|
4026
|
+
try {
|
|
4027
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
4028
|
+
} catch {
|
|
4029
|
+
return false;
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
// src/hooks/pre-compact.ts
|
|
4000
4034
|
async function main() {
|
|
4001
4035
|
try {
|
|
4002
4036
|
const input = await readStdin();
|
|
@@ -4103,4 +4137,6 @@ function readStdin() {
|
|
|
4103
4137
|
setTimeout(() => resolve5(data), 3e3);
|
|
4104
4138
|
});
|
|
4105
4139
|
}
|
|
4106
|
-
|
|
4140
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
4141
|
+
main();
|
|
4142
|
+
}
|
|
@@ -1167,6 +1167,13 @@ var init_config = __esm({
|
|
|
1167
1167
|
});
|
|
1168
1168
|
LanguageFrameworkEntrySchema = z2.object({
|
|
1169
1169
|
framework: z2.string().optional(),
|
|
1170
|
+
// `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
|
|
1171
|
+
// by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
|
|
1172
|
+
// `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
|
|
1173
|
+
// derives the index builders' candidate set from it, so it is typed here rather than
|
|
1174
|
+
// read as an unknown: a consumer that has to shape-check its own SoT is one branch away
|
|
1175
|
+
// from silently contributing `undefined` to a path predicate.
|
|
1176
|
+
source_dirs: z2.array(z2.string()).optional(),
|
|
1170
1177
|
test_framework: z2.string().optional(),
|
|
1171
1178
|
test: z2.string().optional(),
|
|
1172
1179
|
runtime: z2.string().optional(),
|
|
@@ -1339,6 +1346,16 @@ var init_memory_vector = __esm({
|
|
|
1339
1346
|
}
|
|
1340
1347
|
});
|
|
1341
1348
|
|
|
1349
|
+
// src/lib/timestamps.ts
|
|
1350
|
+
function nowIso() {
|
|
1351
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1352
|
+
}
|
|
1353
|
+
var init_timestamps = __esm({
|
|
1354
|
+
"src/lib/timestamps.ts"() {
|
|
1355
|
+
"use strict";
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1342
1359
|
// src/db-backup.ts
|
|
1343
1360
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1344
1361
|
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
@@ -3670,8 +3687,8 @@ function linkSessionToTask(db, sessionId, taskId) {
|
|
|
3670
3687
|
}
|
|
3671
3688
|
function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
|
|
3672
3689
|
const result = db.prepare(`
|
|
3673
|
-
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
|
|
3674
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3690
|
+
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
|
|
3691
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3675
3692
|
`).run(
|
|
3676
3693
|
sessionId,
|
|
3677
3694
|
turnNumber,
|
|
@@ -3680,14 +3697,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
|
|
|
3680
3697
|
toolCallsJson,
|
|
3681
3698
|
toolCallCount,
|
|
3682
3699
|
promptTokens,
|
|
3683
|
-
responseTokens
|
|
3700
|
+
responseTokens,
|
|
3701
|
+
nowIso()
|
|
3684
3702
|
);
|
|
3685
3703
|
return Number(result.lastInsertRowid);
|
|
3686
3704
|
}
|
|
3687
3705
|
function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
|
|
3688
3706
|
db.prepare(`
|
|
3689
|
-
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
|
|
3690
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3707
|
+
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved, created_at)
|
|
3708
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3691
3709
|
`).run(
|
|
3692
3710
|
sessionId,
|
|
3693
3711
|
turnNumber,
|
|
@@ -3696,7 +3714,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
|
|
|
3696
3714
|
inputSize,
|
|
3697
3715
|
outputSize,
|
|
3698
3716
|
success ? 1 : 0,
|
|
3699
|
-
filesInvolved ? JSON.stringify(filesInvolved) : null
|
|
3717
|
+
filesInvolved ? JSON.stringify(filesInvolved) : null,
|
|
3718
|
+
nowIso()
|
|
3700
3719
|
);
|
|
3701
3720
|
}
|
|
3702
3721
|
function getLastProcessedLine(db, sessionId) {
|
|
@@ -3955,6 +3974,7 @@ var init_memory_db = __esm({
|
|
|
3955
3974
|
init_db_driver();
|
|
3956
3975
|
init_config();
|
|
3957
3976
|
init_memory_vector();
|
|
3977
|
+
init_timestamps();
|
|
3958
3978
|
init_db_backup();
|
|
3959
3979
|
init_rule_delivery();
|
|
3960
3980
|
init_rule_candidate_store();
|
|
@@ -4071,13 +4091,105 @@ function getFeatureImpact(db, filePaths) {
|
|
|
4071
4091
|
// src/hooks/pre-delete-check.ts
|
|
4072
4092
|
init_config();
|
|
4073
4093
|
|
|
4094
|
+
// src/lib/source-layout.ts
|
|
4095
|
+
init_config();
|
|
4096
|
+
function normalizeDir(raw) {
|
|
4097
|
+
if (typeof raw !== "string") return null;
|
|
4098
|
+
const posix = raw.replace(/\\/g, "/");
|
|
4099
|
+
if (posix.startsWith("/") || /^[A-Za-z]:\//.test(posix)) return null;
|
|
4100
|
+
const parts = posix.split("/").filter((p) => p !== "" && p !== ".");
|
|
4101
|
+
if (parts.some((p) => p === "..")) return null;
|
|
4102
|
+
if (parts.length === 0) return ".";
|
|
4103
|
+
return parts.join("/");
|
|
4104
|
+
}
|
|
4105
|
+
function declaredLanguageSourceDirs() {
|
|
4106
|
+
const languages = getConfig().framework.languages;
|
|
4107
|
+
if (!languages) return [];
|
|
4108
|
+
const out = [];
|
|
4109
|
+
for (const entry of Object.values(languages)) {
|
|
4110
|
+
if (Array.isArray(entry.source_dirs)) out.push(...entry.source_dirs);
|
|
4111
|
+
}
|
|
4112
|
+
return out;
|
|
4113
|
+
}
|
|
4114
|
+
function getSourceLayout() {
|
|
4115
|
+
const config = getConfig();
|
|
4116
|
+
const paths = config.paths;
|
|
4117
|
+
const declared = [
|
|
4118
|
+
paths.source,
|
|
4119
|
+
...paths.monorepo_roots ?? [],
|
|
4120
|
+
...declaredLanguageSourceDirs()
|
|
4121
|
+
];
|
|
4122
|
+
const normalized = declared.map(normalizeDir).filter((d) => d !== null);
|
|
4123
|
+
const includesRoot = normalized.includes(".");
|
|
4124
|
+
const sourceDirs = includesRoot ? [] : [...new Set(normalized)].sort();
|
|
4125
|
+
const source = normalizeDir(paths.source) ?? ".";
|
|
4126
|
+
const under = (child) => source === "." ? child : `${source}/${child}`;
|
|
4127
|
+
return {
|
|
4128
|
+
sourceDirs,
|
|
4129
|
+
includesRoot,
|
|
4130
|
+
extensions: getResolvedPaths().extensions,
|
|
4131
|
+
pagesDir: normalizeDir(paths.pages ?? under("app")) ?? under("app"),
|
|
4132
|
+
componentsDir: normalizeDir(paths.components ?? under("components")) ?? under("components")
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
4135
|
+
function isUnderSourceDir(path, layout = getSourceLayout()) {
|
|
4136
|
+
if (normalizeDir(path) === null) return false;
|
|
4137
|
+
if (layout.includesRoot) return true;
|
|
4138
|
+
return layout.sourceDirs.some((d) => path.startsWith(`${d}/`));
|
|
4139
|
+
}
|
|
4140
|
+
|
|
4074
4141
|
// src/hooks/lib/write-hook-message.ts
|
|
4075
|
-
|
|
4076
|
-
|
|
4142
|
+
var HOOK_EVENTS = [
|
|
4143
|
+
"UserPromptSubmit",
|
|
4144
|
+
"UserPromptExpansion",
|
|
4145
|
+
"SessionStart",
|
|
4146
|
+
"PreToolUse",
|
|
4147
|
+
"PostToolUse",
|
|
4148
|
+
"Stop",
|
|
4149
|
+
"SubagentStop",
|
|
4150
|
+
"PreCompact"
|
|
4151
|
+
];
|
|
4152
|
+
var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
|
|
4153
|
+
function isHookEvent(value) {
|
|
4154
|
+
return typeof value === "string" && HOOK_EVENTS.includes(value);
|
|
4155
|
+
}
|
|
4156
|
+
function writeHookContext(hookEventName, message) {
|
|
4157
|
+
if (!isHookEvent(hookEventName)) {
|
|
4158
|
+
process.stderr.write(
|
|
4159
|
+
`[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
|
|
4160
|
+
`
|
|
4161
|
+
);
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
4164
|
+
if (typeof message !== "string" || message.length === 0) return;
|
|
4165
|
+
let additionalContext = message;
|
|
4166
|
+
if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
|
|
4167
|
+
const marker = `
|
|
4168
|
+
[massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
|
|
4169
|
+
additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
|
|
4170
|
+
}
|
|
4171
|
+
process.stdout.write(
|
|
4172
|
+
JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
|
|
4173
|
+
);
|
|
4077
4174
|
}
|
|
4078
4175
|
|
|
4079
4176
|
// src/hooks/pre-delete-check.ts
|
|
4080
4177
|
init_hook_failure_signal();
|
|
4178
|
+
|
|
4179
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
4180
|
+
import { pathToFileURL } from "url";
|
|
4181
|
+
function isDirectInvocation(moduleUrl) {
|
|
4182
|
+
const entry = process.argv[1];
|
|
4183
|
+
if (!entry) return false;
|
|
4184
|
+
try {
|
|
4185
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
4186
|
+
} catch {
|
|
4187
|
+
return false;
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
|
|
4191
|
+
// src/hooks/pre-delete-check.ts
|
|
4192
|
+
var HOOK_EVENT = "PreToolUse";
|
|
4081
4193
|
var PROJECT_ROOT2 = getProjectRoot();
|
|
4082
4194
|
var KNOWLEDGE_PROTECTED_FILES = [
|
|
4083
4195
|
"knowledge-db.ts",
|
|
@@ -4127,8 +4239,8 @@ function extractDeletedFiles(input) {
|
|
|
4127
4239
|
if (rmMatch) {
|
|
4128
4240
|
const paths = rmMatch[1].split(/\s+/).filter((p) => !p.startsWith("-"));
|
|
4129
4241
|
for (const p of paths) {
|
|
4130
|
-
const relPath = p
|
|
4131
|
-
if (relPath
|
|
4242
|
+
const relPath = isUnderSourceDir(p) ? p : p.replace(PROJECT_ROOT2 + "/", "");
|
|
4243
|
+
if (isUnderSourceDir(relPath) || relPath.endsWith(".py")) {
|
|
4132
4244
|
files.push(relPath);
|
|
4133
4245
|
}
|
|
4134
4246
|
}
|
|
@@ -4138,7 +4250,7 @@ function extractDeletedFiles(input) {
|
|
|
4138
4250
|
const content = input.tool_input.content || "";
|
|
4139
4251
|
if (content.trim().length === 0) {
|
|
4140
4252
|
const relPath = input.tool_input.file_path.replace(PROJECT_ROOT2 + "/", "");
|
|
4141
|
-
if (relPath
|
|
4253
|
+
if (isUnderSourceDir(relPath) || relPath.endsWith(".py")) {
|
|
4142
4254
|
files.push(relPath);
|
|
4143
4255
|
}
|
|
4144
4256
|
}
|
|
@@ -4221,7 +4333,7 @@ async function main() {
|
|
|
4221
4333
|
const input = await readStdin();
|
|
4222
4334
|
const hookInput = JSON.parse(input);
|
|
4223
4335
|
const messages = runPreDeleteChecks(hookInput);
|
|
4224
|
-
for (const msg of messages)
|
|
4336
|
+
for (const msg of messages) writeHookContext(HOOK_EVENT, msg);
|
|
4225
4337
|
} catch (err) {
|
|
4226
4338
|
recordHookFailure("pre-delete-check", err);
|
|
4227
4339
|
process.stderr.write("MASSU SECURITY GATE \u2014 BLOCKED (gate could not evaluate this call)\n");
|
|
@@ -4240,7 +4352,7 @@ function readStdin() {
|
|
|
4240
4352
|
setTimeout(() => resolve5(data), 400);
|
|
4241
4353
|
});
|
|
4242
4354
|
}
|
|
4243
|
-
if (
|
|
4355
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
4244
4356
|
main();
|
|
4245
4357
|
}
|
|
4246
4358
|
export {
|