@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();
|
|
@@ -3973,12 +3993,57 @@ var init_hook_failure_signal = __esm({
|
|
|
3973
3993
|
});
|
|
3974
3994
|
|
|
3975
3995
|
// src/hooks/lib/write-hook-message.ts
|
|
3976
|
-
|
|
3977
|
-
|
|
3996
|
+
var HOOK_EVENTS = [
|
|
3997
|
+
"UserPromptSubmit",
|
|
3998
|
+
"UserPromptExpansion",
|
|
3999
|
+
"SessionStart",
|
|
4000
|
+
"PreToolUse",
|
|
4001
|
+
"PostToolUse",
|
|
4002
|
+
"Stop",
|
|
4003
|
+
"SubagentStop",
|
|
4004
|
+
"PreCompact"
|
|
4005
|
+
];
|
|
4006
|
+
var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
|
|
4007
|
+
function isHookEvent(value) {
|
|
4008
|
+
return typeof value === "string" && HOOK_EVENTS.includes(value);
|
|
4009
|
+
}
|
|
4010
|
+
function writeHookContext(hookEventName, message) {
|
|
4011
|
+
if (!isHookEvent(hookEventName)) {
|
|
4012
|
+
process.stderr.write(
|
|
4013
|
+
`[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
|
|
4014
|
+
`
|
|
4015
|
+
);
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
4018
|
+
if (typeof message !== "string" || message.length === 0) return;
|
|
4019
|
+
let additionalContext = message;
|
|
4020
|
+
if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
|
|
4021
|
+
const marker = `
|
|
4022
|
+
[massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
|
|
4023
|
+
additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
|
|
4024
|
+
}
|
|
4025
|
+
process.stdout.write(
|
|
4026
|
+
JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
|
|
4027
|
+
);
|
|
3978
4028
|
}
|
|
3979
4029
|
|
|
3980
4030
|
// src/hooks/security-gate.ts
|
|
3981
4031
|
init_hook_failure_signal();
|
|
4032
|
+
|
|
4033
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
4034
|
+
import { pathToFileURL } from "url";
|
|
4035
|
+
function isDirectInvocation(moduleUrl) {
|
|
4036
|
+
const entry = process.argv[1];
|
|
4037
|
+
if (!entry) return false;
|
|
4038
|
+
try {
|
|
4039
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
4040
|
+
} catch {
|
|
4041
|
+
return false;
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4045
|
+
// src/hooks/security-gate.ts
|
|
4046
|
+
var HOOK_EVENT = "PreToolUse";
|
|
3982
4047
|
var DANGEROUS_BASH_PATTERNS = [
|
|
3983
4048
|
// BLOCK — destructive and irreversible, or arbitrary remote code execution.
|
|
3984
4049
|
{ pattern: /rm\s+-[a-z]*r[a-z]*f[a-z]*\s+\/(?:\s|$)/, label: "rm -rf /", severity: "block" },
|
|
@@ -4098,7 +4163,7 @@ async function main() {
|
|
|
4098
4163
|
const input = await readStdin();
|
|
4099
4164
|
const hookInput = JSON.parse(input);
|
|
4100
4165
|
const messages = runSecurityGateChecks(hookInput);
|
|
4101
|
-
for (const msg of messages)
|
|
4166
|
+
for (const msg of messages) writeHookContext(HOOK_EVENT, msg);
|
|
4102
4167
|
} catch (err) {
|
|
4103
4168
|
recordHookFailure("security-gate", err);
|
|
4104
4169
|
process.stderr.write("MASSU SECURITY GATE \u2014 BLOCKED (gate could not evaluate this call)\n");
|
|
@@ -4117,7 +4182,7 @@ function readStdin() {
|
|
|
4117
4182
|
setTimeout(() => resolve5(data), 400);
|
|
4118
4183
|
});
|
|
4119
4184
|
}
|
|
4120
|
-
if (
|
|
4185
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
4121
4186
|
main();
|
|
4122
4187
|
}
|
|
4123
4188
|
|
|
@@ -4219,7 +4284,57 @@ function getFeatureImpact(db, filePaths) {
|
|
|
4219
4284
|
|
|
4220
4285
|
// src/hooks/pre-delete-check.ts
|
|
4221
4286
|
init_config();
|
|
4287
|
+
|
|
4288
|
+
// src/lib/source-layout.ts
|
|
4289
|
+
init_config();
|
|
4290
|
+
function normalizeDir(raw) {
|
|
4291
|
+
if (typeof raw !== "string") return null;
|
|
4292
|
+
const posix = raw.replace(/\\/g, "/");
|
|
4293
|
+
if (posix.startsWith("/") || /^[A-Za-z]:\//.test(posix)) return null;
|
|
4294
|
+
const parts = posix.split("/").filter((p) => p !== "" && p !== ".");
|
|
4295
|
+
if (parts.some((p) => p === "..")) return null;
|
|
4296
|
+
if (parts.length === 0) return ".";
|
|
4297
|
+
return parts.join("/");
|
|
4298
|
+
}
|
|
4299
|
+
function declaredLanguageSourceDirs() {
|
|
4300
|
+
const languages = getConfig().framework.languages;
|
|
4301
|
+
if (!languages) return [];
|
|
4302
|
+
const out = [];
|
|
4303
|
+
for (const entry of Object.values(languages)) {
|
|
4304
|
+
if (Array.isArray(entry.source_dirs)) out.push(...entry.source_dirs);
|
|
4305
|
+
}
|
|
4306
|
+
return out;
|
|
4307
|
+
}
|
|
4308
|
+
function getSourceLayout() {
|
|
4309
|
+
const config = getConfig();
|
|
4310
|
+
const paths = config.paths;
|
|
4311
|
+
const declared = [
|
|
4312
|
+
paths.source,
|
|
4313
|
+
...paths.monorepo_roots ?? [],
|
|
4314
|
+
...declaredLanguageSourceDirs()
|
|
4315
|
+
];
|
|
4316
|
+
const normalized = declared.map(normalizeDir).filter((d) => d !== null);
|
|
4317
|
+
const includesRoot = normalized.includes(".");
|
|
4318
|
+
const sourceDirs = includesRoot ? [] : [...new Set(normalized)].sort();
|
|
4319
|
+
const source = normalizeDir(paths.source) ?? ".";
|
|
4320
|
+
const under = (child) => source === "." ? child : `${source}/${child}`;
|
|
4321
|
+
return {
|
|
4322
|
+
sourceDirs,
|
|
4323
|
+
includesRoot,
|
|
4324
|
+
extensions: getResolvedPaths().extensions,
|
|
4325
|
+
pagesDir: normalizeDir(paths.pages ?? under("app")) ?? under("app"),
|
|
4326
|
+
componentsDir: normalizeDir(paths.components ?? under("components")) ?? under("components")
|
|
4327
|
+
};
|
|
4328
|
+
}
|
|
4329
|
+
function isUnderSourceDir(path, layout = getSourceLayout()) {
|
|
4330
|
+
if (normalizeDir(path) === null) return false;
|
|
4331
|
+
if (layout.includesRoot) return true;
|
|
4332
|
+
return layout.sourceDirs.some((d) => path.startsWith(`${d}/`));
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4335
|
+
// src/hooks/pre-delete-check.ts
|
|
4222
4336
|
init_hook_failure_signal();
|
|
4337
|
+
var HOOK_EVENT2 = "PreToolUse";
|
|
4223
4338
|
var PROJECT_ROOT2 = getProjectRoot();
|
|
4224
4339
|
var KNOWLEDGE_PROTECTED_FILES = [
|
|
4225
4340
|
"knowledge-db.ts",
|
|
@@ -4269,8 +4384,8 @@ function extractDeletedFiles(input) {
|
|
|
4269
4384
|
if (rmMatch) {
|
|
4270
4385
|
const paths = rmMatch[1].split(/\s+/).filter((p) => !p.startsWith("-"));
|
|
4271
4386
|
for (const p of paths) {
|
|
4272
|
-
const relPath = p
|
|
4273
|
-
if (relPath
|
|
4387
|
+
const relPath = isUnderSourceDir(p) ? p : p.replace(PROJECT_ROOT2 + "/", "");
|
|
4388
|
+
if (isUnderSourceDir(relPath) || relPath.endsWith(".py")) {
|
|
4274
4389
|
files.push(relPath);
|
|
4275
4390
|
}
|
|
4276
4391
|
}
|
|
@@ -4280,7 +4395,7 @@ function extractDeletedFiles(input) {
|
|
|
4280
4395
|
const content = input.tool_input.content || "";
|
|
4281
4396
|
if (content.trim().length === 0) {
|
|
4282
4397
|
const relPath = input.tool_input.file_path.replace(PROJECT_ROOT2 + "/", "");
|
|
4283
|
-
if (relPath
|
|
4398
|
+
if (isUnderSourceDir(relPath) || relPath.endsWith(".py")) {
|
|
4284
4399
|
files.push(relPath);
|
|
4285
4400
|
}
|
|
4286
4401
|
}
|
|
@@ -4363,7 +4478,7 @@ async function main2() {
|
|
|
4363
4478
|
const input = await readStdin2();
|
|
4364
4479
|
const hookInput = JSON.parse(input);
|
|
4365
4480
|
const messages = runPreDeleteChecks(hookInput);
|
|
4366
|
-
for (const msg of messages)
|
|
4481
|
+
for (const msg of messages) writeHookContext(HOOK_EVENT2, msg);
|
|
4367
4482
|
} catch (err) {
|
|
4368
4483
|
recordHookFailure("pre-delete-check", err);
|
|
4369
4484
|
process.stderr.write("MASSU SECURITY GATE \u2014 BLOCKED (gate could not evaluate this call)\n");
|
|
@@ -4382,7 +4497,7 @@ function readStdin2() {
|
|
|
4382
4497
|
setTimeout(() => resolve5(data), 400);
|
|
4383
4498
|
});
|
|
4384
4499
|
}
|
|
4385
|
-
if (
|
|
4500
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
4386
4501
|
main2();
|
|
4387
4502
|
}
|
|
4388
4503
|
|
|
@@ -4423,6 +4538,7 @@ function readStdinToEof(deadlineMs = 4e3) {
|
|
|
4423
4538
|
|
|
4424
4539
|
// src/hooks/pre-tool-use-gate.ts
|
|
4425
4540
|
init_hook_failure_signal();
|
|
4541
|
+
var HOOK_EVENT3 = "PreToolUse";
|
|
4426
4542
|
var EXIT_DENY = 2;
|
|
4427
4543
|
var EXIT_ALLOW = 0;
|
|
4428
4544
|
function deny(reason, detail) {
|
|
@@ -4472,11 +4588,13 @@ async function main3() {
|
|
|
4472
4588
|
}
|
|
4473
4589
|
const blocking = findings.filter((f) => f.severity === "block");
|
|
4474
4590
|
for (const f of findings.filter((x) => x.severity === "warn")) {
|
|
4475
|
-
|
|
4591
|
+
writeHookContext(HOOK_EVENT3, f.message);
|
|
4476
4592
|
}
|
|
4477
4593
|
if (blocking.length > 0) {
|
|
4478
4594
|
deny(blocking.map((f) => f.message).join("\n\n"));
|
|
4479
4595
|
}
|
|
4480
4596
|
process.exit(EXIT_ALLOW);
|
|
4481
4597
|
}
|
|
4482
|
-
|
|
4598
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
4599
|
+
main3();
|
|
4600
|
+
}
|
|
@@ -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();
|
|
@@ -4072,6 +4092,21 @@ function toolResponseText(raw) {
|
|
|
4072
4092
|
|
|
4073
4093
|
// src/hooks/quality-event.ts
|
|
4074
4094
|
init_hook_failure_signal();
|
|
4095
|
+
init_timestamps();
|
|
4096
|
+
|
|
4097
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
4098
|
+
import { pathToFileURL } from "url";
|
|
4099
|
+
function isDirectInvocation(moduleUrl) {
|
|
4100
|
+
const entry = process.argv[1];
|
|
4101
|
+
if (!entry) return false;
|
|
4102
|
+
try {
|
|
4103
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
4104
|
+
} catch {
|
|
4105
|
+
return false;
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
// src/hooks/quality-event.ts
|
|
4075
4110
|
var TEST_FAILURE_PATTERNS = [
|
|
4076
4111
|
/\bFAIL\b/,
|
|
4077
4112
|
/✗/,
|
|
@@ -4135,11 +4170,11 @@ async function main() {
|
|
|
4135
4170
|
const db = getMemoryDb();
|
|
4136
4171
|
try {
|
|
4137
4172
|
const stmt = db.prepare(`
|
|
4138
|
-
INSERT INTO quality_events (session_id, event_type, tool_name, details)
|
|
4139
|
-
VALUES (?, ?, ?, ?)
|
|
4173
|
+
INSERT INTO quality_events (session_id, event_type, tool_name, details, created_at)
|
|
4174
|
+
VALUES (?, ?, ?, ?, ?)
|
|
4140
4175
|
`);
|
|
4141
4176
|
for (const signal of signals) {
|
|
4142
|
-
stmt.run(session_id, signal.event_type, tool_name, signal.details);
|
|
4177
|
+
stmt.run(session_id, signal.event_type, tool_name, signal.details, nowIso());
|
|
4143
4178
|
}
|
|
4144
4179
|
} finally {
|
|
4145
4180
|
db.close();
|
|
@@ -4160,4 +4195,6 @@ function readStdin() {
|
|
|
4160
4195
|
setTimeout(() => resolve5(data), 3e3);
|
|
4161
4196
|
});
|
|
4162
4197
|
}
|
|
4163
|
-
|
|
4198
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
4199
|
+
main();
|
|
4200
|
+
}
|
|
@@ -697,6 +697,13 @@ var init_config = __esm({
|
|
|
697
697
|
});
|
|
698
698
|
LanguageFrameworkEntrySchema = z2.object({
|
|
699
699
|
framework: z2.string().optional(),
|
|
700
|
+
// `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
|
|
701
|
+
// by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
|
|
702
|
+
// `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
|
|
703
|
+
// derives the index builders' candidate set from it, so it is typed here rather than
|
|
704
|
+
// read as an unknown: a consumer that has to shape-check its own SoT is one branch away
|
|
705
|
+
// from silently contributing `undefined` to a path predicate.
|
|
706
|
+
source_dirs: z2.array(z2.string()).optional(),
|
|
700
707
|
test_framework: z2.string().optional(),
|
|
701
708
|
test: z2.string().optional(),
|
|
702
709
|
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();
|
|
@@ -3978,12 +3998,57 @@ import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync a
|
|
|
3978
3998
|
import { basename as basename3, resolve as resolve5 } from "path";
|
|
3979
3999
|
|
|
3980
4000
|
// src/hooks/lib/write-hook-message.ts
|
|
3981
|
-
|
|
3982
|
-
|
|
4001
|
+
var HOOK_EVENTS = [
|
|
4002
|
+
"UserPromptSubmit",
|
|
4003
|
+
"UserPromptExpansion",
|
|
4004
|
+
"SessionStart",
|
|
4005
|
+
"PreToolUse",
|
|
4006
|
+
"PostToolUse",
|
|
4007
|
+
"Stop",
|
|
4008
|
+
"SubagentStop",
|
|
4009
|
+
"PreCompact"
|
|
4010
|
+
];
|
|
4011
|
+
var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
|
|
4012
|
+
function isHookEvent(value) {
|
|
4013
|
+
return typeof value === "string" && HOOK_EVENTS.includes(value);
|
|
4014
|
+
}
|
|
4015
|
+
function writeHookContext(hookEventName, message) {
|
|
4016
|
+
if (!isHookEvent(hookEventName)) {
|
|
4017
|
+
process.stderr.write(
|
|
4018
|
+
`[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
|
|
4019
|
+
`
|
|
4020
|
+
);
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
if (typeof message !== "string" || message.length === 0) return;
|
|
4024
|
+
let additionalContext = message;
|
|
4025
|
+
if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
|
|
4026
|
+
const marker = `
|
|
4027
|
+
[massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
|
|
4028
|
+
additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
|
|
4029
|
+
}
|
|
4030
|
+
process.stdout.write(
|
|
4031
|
+
JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
|
|
4032
|
+
);
|
|
3983
4033
|
}
|
|
3984
4034
|
|
|
3985
4035
|
// src/hooks/rule-enforcement-pipeline.ts
|
|
3986
4036
|
init_hook_failure_signal();
|
|
4037
|
+
|
|
4038
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
4039
|
+
import { pathToFileURL } from "url";
|
|
4040
|
+
function isDirectInvocation(moduleUrl) {
|
|
4041
|
+
const entry = process.argv[1];
|
|
4042
|
+
if (!entry) return false;
|
|
4043
|
+
try {
|
|
4044
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
4045
|
+
} catch {
|
|
4046
|
+
return false;
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
// src/hooks/rule-enforcement-pipeline.ts
|
|
4051
|
+
var HOOK_EVENT = "PostToolUse";
|
|
3987
4052
|
async function main() {
|
|
3988
4053
|
try {
|
|
3989
4054
|
const input = await readStdin();
|
|
@@ -4081,7 +4146,7 @@ async function main() {
|
|
|
4081
4146
|
lines.push(" This step is MANDATORY per the auto-learning pipeline.");
|
|
4082
4147
|
lines.push("============================================================================");
|
|
4083
4148
|
lines.push("");
|
|
4084
|
-
|
|
4149
|
+
writeHookContext(HOOK_EVENT, lines.join("\n"));
|
|
4085
4150
|
} catch (err) {
|
|
4086
4151
|
recordHookFailure("rule-enforcement-pipeline", err);
|
|
4087
4152
|
}
|
|
@@ -4098,4 +4163,6 @@ function readStdin() {
|
|
|
4098
4163
|
setTimeout(() => resolve6(data), 3e3);
|
|
4099
4164
|
});
|
|
4100
4165
|
}
|
|
4101
|
-
|
|
4166
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
4167
|
+
main();
|
|
4168
|
+
}
|