@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
|
export {
|
|
@@ -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(),
|
|
@@ -1360,6 +1367,16 @@ var init_memory_vector = __esm({
|
|
|
1360
1367
|
}
|
|
1361
1368
|
});
|
|
1362
1369
|
|
|
1370
|
+
// src/lib/timestamps.ts
|
|
1371
|
+
function nowIso() {
|
|
1372
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1373
|
+
}
|
|
1374
|
+
var init_timestamps = __esm({
|
|
1375
|
+
"src/lib/timestamps.ts"() {
|
|
1376
|
+
"use strict";
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1363
1380
|
// src/db-backup.ts
|
|
1364
1381
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1365
1382
|
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
@@ -3853,8 +3870,8 @@ function linkSessionToTask(db, sessionId, taskId) {
|
|
|
3853
3870
|
}
|
|
3854
3871
|
function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
|
|
3855
3872
|
const result = db.prepare(`
|
|
3856
|
-
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
|
|
3857
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3873
|
+
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
|
|
3874
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3858
3875
|
`).run(
|
|
3859
3876
|
sessionId,
|
|
3860
3877
|
turnNumber,
|
|
@@ -3863,14 +3880,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
|
|
|
3863
3880
|
toolCallsJson,
|
|
3864
3881
|
toolCallCount,
|
|
3865
3882
|
promptTokens,
|
|
3866
|
-
responseTokens
|
|
3883
|
+
responseTokens,
|
|
3884
|
+
nowIso()
|
|
3867
3885
|
);
|
|
3868
3886
|
return Number(result.lastInsertRowid);
|
|
3869
3887
|
}
|
|
3870
3888
|
function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
|
|
3871
3889
|
db.prepare(`
|
|
3872
|
-
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
|
|
3873
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3890
|
+
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)
|
|
3891
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3874
3892
|
`).run(
|
|
3875
3893
|
sessionId,
|
|
3876
3894
|
turnNumber,
|
|
@@ -3879,7 +3897,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
|
|
|
3879
3897
|
inputSize,
|
|
3880
3898
|
outputSize,
|
|
3881
3899
|
success ? 1 : 0,
|
|
3882
|
-
filesInvolved ? JSON.stringify(filesInvolved) : null
|
|
3900
|
+
filesInvolved ? JSON.stringify(filesInvolved) : null,
|
|
3901
|
+
nowIso()
|
|
3883
3902
|
);
|
|
3884
3903
|
}
|
|
3885
3904
|
function getLastProcessedLine(db, sessionId) {
|
|
@@ -4138,6 +4157,7 @@ var init_memory_db = __esm({
|
|
|
4138
4157
|
init_db_driver();
|
|
4139
4158
|
init_config();
|
|
4140
4159
|
init_memory_vector();
|
|
4160
|
+
init_timestamps();
|
|
4141
4161
|
init_db_backup();
|
|
4142
4162
|
init_rule_delivery();
|
|
4143
4163
|
init_rule_candidate_store();
|
|
@@ -7348,6 +7368,20 @@ async function runConsolidation(db, opts = {}) {
|
|
|
7348
7368
|
|
|
7349
7369
|
// src/hooks/session-end.ts
|
|
7350
7370
|
init_hook_failure_signal();
|
|
7371
|
+
|
|
7372
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
7373
|
+
import { pathToFileURL } from "url";
|
|
7374
|
+
function isDirectInvocation(moduleUrl) {
|
|
7375
|
+
const entry = process.argv[1];
|
|
7376
|
+
if (!entry) return false;
|
|
7377
|
+
try {
|
|
7378
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
7379
|
+
} catch {
|
|
7380
|
+
return false;
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
7383
|
+
|
|
7384
|
+
// src/hooks/session-end.ts
|
|
7351
7385
|
async function main() {
|
|
7352
7386
|
try {
|
|
7353
7387
|
const input = await readStdin();
|
|
@@ -7707,4 +7741,6 @@ function readStdin() {
|
|
|
7707
7741
|
setTimeout(() => resolve8(data), 5e3);
|
|
7708
7742
|
});
|
|
7709
7743
|
}
|
|
7710
|
-
|
|
7744
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
7745
|
+
main();
|
|
7746
|
+
}
|
|
@@ -1190,6 +1190,13 @@ var init_config = __esm({
|
|
|
1190
1190
|
});
|
|
1191
1191
|
LanguageFrameworkEntrySchema = z2.object({
|
|
1192
1192
|
framework: z2.string().optional(),
|
|
1193
|
+
// `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
|
|
1194
|
+
// by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
|
|
1195
|
+
// `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
|
|
1196
|
+
// derives the index builders' candidate set from it, so it is typed here rather than
|
|
1197
|
+
// read as an unknown: a consumer that has to shape-check its own SoT is one branch away
|
|
1198
|
+
// from silently contributing `undefined` to a path predicate.
|
|
1199
|
+
source_dirs: z2.array(z2.string()).optional(),
|
|
1193
1200
|
test_framework: z2.string().optional(),
|
|
1194
1201
|
test: z2.string().optional(),
|
|
1195
1202
|
runtime: z2.string().optional(),
|
|
@@ -1362,6 +1369,16 @@ var init_memory_vector = __esm({
|
|
|
1362
1369
|
}
|
|
1363
1370
|
});
|
|
1364
1371
|
|
|
1372
|
+
// src/lib/timestamps.ts
|
|
1373
|
+
function nowIso() {
|
|
1374
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1375
|
+
}
|
|
1376
|
+
var init_timestamps = __esm({
|
|
1377
|
+
"src/lib/timestamps.ts"() {
|
|
1378
|
+
"use strict";
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1365
1382
|
// src/db-backup.ts
|
|
1366
1383
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1367
1384
|
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
@@ -3693,8 +3710,8 @@ function linkSessionToTask(db, sessionId, taskId) {
|
|
|
3693
3710
|
}
|
|
3694
3711
|
function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
|
|
3695
3712
|
const result = db.prepare(`
|
|
3696
|
-
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
|
|
3697
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3713
|
+
INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
|
|
3714
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3698
3715
|
`).run(
|
|
3699
3716
|
sessionId,
|
|
3700
3717
|
turnNumber,
|
|
@@ -3703,14 +3720,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
|
|
|
3703
3720
|
toolCallsJson,
|
|
3704
3721
|
toolCallCount,
|
|
3705
3722
|
promptTokens,
|
|
3706
|
-
responseTokens
|
|
3723
|
+
responseTokens,
|
|
3724
|
+
nowIso()
|
|
3707
3725
|
);
|
|
3708
3726
|
return Number(result.lastInsertRowid);
|
|
3709
3727
|
}
|
|
3710
3728
|
function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
|
|
3711
3729
|
db.prepare(`
|
|
3712
|
-
INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
|
|
3713
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3730
|
+
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)
|
|
3731
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3714
3732
|
`).run(
|
|
3715
3733
|
sessionId,
|
|
3716
3734
|
turnNumber,
|
|
@@ -3719,7 +3737,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
|
|
|
3719
3737
|
inputSize,
|
|
3720
3738
|
outputSize,
|
|
3721
3739
|
success ? 1 : 0,
|
|
3722
|
-
filesInvolved ? JSON.stringify(filesInvolved) : null
|
|
3740
|
+
filesInvolved ? JSON.stringify(filesInvolved) : null,
|
|
3741
|
+
nowIso()
|
|
3723
3742
|
);
|
|
3724
3743
|
}
|
|
3725
3744
|
function getLastProcessedLine(db, sessionId) {
|
|
@@ -3978,6 +3997,7 @@ var init_memory_db = __esm({
|
|
|
3978
3997
|
init_db_driver();
|
|
3979
3998
|
init_config();
|
|
3980
3999
|
init_memory_vector();
|
|
4000
|
+
init_timestamps();
|
|
3981
4001
|
init_db_backup();
|
|
3982
4002
|
init_rule_delivery();
|
|
3983
4003
|
init_rule_candidate_store();
|
|
@@ -4985,6 +5005,7 @@ var init_memory_files_config = __esm({
|
|
|
4985
5005
|
// src/memory-renderer.ts
|
|
4986
5006
|
var memory_renderer_exports = {};
|
|
4987
5007
|
__export(memory_renderer_exports, {
|
|
5008
|
+
RenderAccountingLeak: () => RenderAccountingLeak,
|
|
4988
5009
|
composeFile: () => composeFile,
|
|
4989
5010
|
renderMemoryFiles: () => renderMemoryFiles,
|
|
4990
5011
|
stripFrontmatter: () => stripFrontmatter
|
|
@@ -4996,7 +5017,7 @@ import { createHash as createHash5 } from "crypto";
|
|
|
4996
5017
|
function renderMemoryFiles(db, candidates, opts) {
|
|
4997
5018
|
const config = opts.config ?? resolveMemoryFilesConfig();
|
|
4998
5019
|
if (!config.renderEnabled) {
|
|
4999
|
-
return EMPTY("render_disabled");
|
|
5020
|
+
return EMPTY("render_disabled", candidates.length);
|
|
5000
5021
|
}
|
|
5001
5022
|
const dryRun = opts.dryRun === true;
|
|
5002
5023
|
const now = opts.now ?? Date.now();
|
|
@@ -5005,7 +5026,7 @@ function renderMemoryFiles(db, candidates, opts) {
|
|
|
5005
5026
|
});
|
|
5006
5027
|
const { memoryDir } = opts;
|
|
5007
5028
|
if (!existsSync19(memoryDir)) {
|
|
5008
|
-
return EMPTY("no_memory_dir");
|
|
5029
|
+
return EMPTY("no_memory_dir", candidates.length);
|
|
5009
5030
|
}
|
|
5010
5031
|
try {
|
|
5011
5032
|
return withMemoryIndexLock(
|
|
@@ -5014,10 +5035,10 @@ function renderMemoryFiles(db, candidates, opts) {
|
|
|
5014
5035
|
);
|
|
5015
5036
|
} catch (err) {
|
|
5016
5037
|
if (err instanceof MemoryIndexLockBusy) {
|
|
5017
|
-
return EMPTY("lock_busy");
|
|
5038
|
+
return EMPTY("lock_busy", candidates.length);
|
|
5018
5039
|
}
|
|
5019
5040
|
audit("memory_file_render_refused", { reason: "unexpected_error", error: String(err) });
|
|
5020
|
-
return EMPTY("error");
|
|
5041
|
+
return EMPTY("error", candidates.length);
|
|
5021
5042
|
}
|
|
5022
5043
|
}
|
|
5023
5044
|
function renderLocked(db, candidates, o) {
|
|
@@ -5027,11 +5048,14 @@ function renderLocked(db, candidates, o) {
|
|
|
5027
5048
|
try {
|
|
5028
5049
|
tombstones = readTombstones(memoryDir);
|
|
5029
5050
|
} catch {
|
|
5030
|
-
return EMPTY("tombstone_ledger_unreadable");
|
|
5051
|
+
return EMPTY("tombstone_ledger_unreadable", candidates.length);
|
|
5031
5052
|
}
|
|
5032
5053
|
const ownerOf = relPathOwnerLookup(db);
|
|
5033
5054
|
const planned = [];
|
|
5034
|
-
|
|
5055
|
+
let unchanged = 0;
|
|
5056
|
+
let capped = 0;
|
|
5057
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
5058
|
+
const c = candidates[i];
|
|
5035
5059
|
if (!isLocalOrigin(c.origin)) {
|
|
5036
5060
|
refusals.push({ name: c.name, reason: "non_local_origin", detail: c.origin || "unknown" });
|
|
5037
5061
|
audit("memory_file_render_refused", { name: c.name, reason: "non_local_origin" });
|
|
@@ -5087,7 +5111,10 @@ function renderLocked(db, candidates, o) {
|
|
|
5087
5111
|
continue;
|
|
5088
5112
|
}
|
|
5089
5113
|
const next = composeFile(c, home);
|
|
5090
|
-
if (next !== null && next === existing)
|
|
5114
|
+
if (next !== null && next === existing) {
|
|
5115
|
+
unchanged++;
|
|
5116
|
+
continue;
|
|
5117
|
+
}
|
|
5091
5118
|
}
|
|
5092
5119
|
const content = composeFile(c, home);
|
|
5093
5120
|
if (content === null) {
|
|
@@ -5095,9 +5122,24 @@ function renderLocked(db, candidates, o) {
|
|
|
5095
5122
|
continue;
|
|
5096
5123
|
}
|
|
5097
5124
|
planned.push({ c, relPath, absPath, content });
|
|
5098
|
-
if (planned.length >= config.renderMaxFilesPerSession)
|
|
5125
|
+
if (planned.length >= config.renderMaxFilesPerSession) {
|
|
5126
|
+
capped = candidates.length - (i + 1);
|
|
5127
|
+
break;
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
const accounted = planned.length + refusals.length + unchanged + capped;
|
|
5131
|
+
if (accounted !== candidates.length) {
|
|
5132
|
+
audit("memory_file_render_refused", {
|
|
5133
|
+
reason: "accounting_leak",
|
|
5134
|
+
considered: candidates.length,
|
|
5135
|
+
accounted
|
|
5136
|
+
});
|
|
5137
|
+
throw new RenderAccountingLeak(
|
|
5138
|
+
`render did not account for every candidate: considered=${candidates.length} but accounted=${accounted} (planned=${planned.length} refused=${refusals.length} unchanged=${unchanged} capped=${capped}). A candidate was dropped without a bucket.`
|
|
5139
|
+
);
|
|
5099
5140
|
}
|
|
5100
5141
|
const indexLines = buildIndexLines(db, memoryDir, planned, config);
|
|
5142
|
+
const books = { considered: candidates.length, unchanged, capped };
|
|
5101
5143
|
if (dryRun) {
|
|
5102
5144
|
return {
|
|
5103
5145
|
enabled: true,
|
|
@@ -5105,11 +5147,20 @@ function renderLocked(db, candidates, o) {
|
|
|
5105
5147
|
written: planned.map((p) => p.relPath),
|
|
5106
5148
|
refusals,
|
|
5107
5149
|
indexLines,
|
|
5108
|
-
bytesWritten: 0
|
|
5150
|
+
bytesWritten: 0,
|
|
5151
|
+
...books
|
|
5109
5152
|
};
|
|
5110
5153
|
}
|
|
5111
5154
|
if (planned.length === 0 && indexLines.length === 0) {
|
|
5112
|
-
return {
|
|
5155
|
+
return {
|
|
5156
|
+
enabled: true,
|
|
5157
|
+
dryRun: false,
|
|
5158
|
+
written: [],
|
|
5159
|
+
refusals,
|
|
5160
|
+
indexLines: [],
|
|
5161
|
+
bytesWritten: 0,
|
|
5162
|
+
...books
|
|
5163
|
+
};
|
|
5113
5164
|
}
|
|
5114
5165
|
if (!hasFreshBackup(memoryDir, home)) {
|
|
5115
5166
|
try {
|
|
@@ -5119,7 +5170,7 @@ function renderLocked(db, candidates, o) {
|
|
|
5119
5170
|
reason: "backup_failed",
|
|
5120
5171
|
error: err instanceof BackupError ? err.message : String(err)
|
|
5121
5172
|
});
|
|
5122
|
-
return EMPTY("backup_failed");
|
|
5173
|
+
return EMPTY("backup_failed", candidates.length);
|
|
5123
5174
|
}
|
|
5124
5175
|
}
|
|
5125
5176
|
const indexPath = o.indexPath ?? join13(memoryDir, "MEMORY.md");
|
|
@@ -5187,7 +5238,10 @@ function renderLocked(db, candidates, o) {
|
|
|
5187
5238
|
errors: restored.errors
|
|
5188
5239
|
});
|
|
5189
5240
|
return {
|
|
5190
|
-
...EMPTY("rollback_incomplete"),
|
|
5241
|
+
...EMPTY("rollback_incomplete", candidates.length),
|
|
5242
|
+
// The books were balanced before the write began; keep them rather than zeroing.
|
|
5243
|
+
unchanged,
|
|
5244
|
+
capped,
|
|
5191
5245
|
enabled: true,
|
|
5192
5246
|
refusals: [
|
|
5193
5247
|
...refusals,
|
|
@@ -5196,9 +5250,9 @@ function renderLocked(db, candidates, o) {
|
|
|
5196
5250
|
};
|
|
5197
5251
|
}
|
|
5198
5252
|
audit("memory_file_render_refused", { reason: "write_failed", error: String(err) });
|
|
5199
|
-
return { ...EMPTY("write_failed"), enabled: true, refusals };
|
|
5253
|
+
return { ...EMPTY("write_failed", candidates.length), enabled: true, refusals, unchanged, capped };
|
|
5200
5254
|
}
|
|
5201
|
-
return { enabled: true, dryRun: false, written, refusals, indexLines, bytesWritten };
|
|
5255
|
+
return { enabled: true, dryRun: false, written, refusals, indexLines, bytesWritten, ...books };
|
|
5202
5256
|
}
|
|
5203
5257
|
function sha256(s) {
|
|
5204
5258
|
return createHash5("sha256").update(s, "utf8").digest("hex");
|
|
@@ -5267,7 +5321,7 @@ function buildIndexLines(db, memoryDir, planned, config) {
|
|
|
5267
5321
|
function oneLine(s) {
|
|
5268
5322
|
return s.replace(/[\r\n]+/g, " ").slice(0, 120);
|
|
5269
5323
|
}
|
|
5270
|
-
var EMPTY;
|
|
5324
|
+
var RenderAccountingLeak, EMPTY;
|
|
5271
5325
|
var init_memory_renderer = __esm({
|
|
5272
5326
|
"src/memory-renderer.ts"() {
|
|
5273
5327
|
"use strict";
|
|
@@ -5282,14 +5336,22 @@ var init_memory_renderer = __esm({
|
|
|
5282
5336
|
init_memory_index_region();
|
|
5283
5337
|
init_memory_files_config();
|
|
5284
5338
|
init_memory_file_ingest();
|
|
5285
|
-
|
|
5339
|
+
RenderAccountingLeak = class extends Error {
|
|
5340
|
+
};
|
|
5341
|
+
EMPTY = (reason, considered = 0) => ({
|
|
5286
5342
|
enabled: false,
|
|
5287
5343
|
dryRun: false,
|
|
5288
5344
|
written: [],
|
|
5289
5345
|
refusals: [],
|
|
5290
5346
|
indexLines: [],
|
|
5291
5347
|
bytesWritten: 0,
|
|
5292
|
-
skippedReason: reason
|
|
5348
|
+
skippedReason: reason,
|
|
5349
|
+
// An early gate refused, so every candidate was dropped — by that ONE named reason.
|
|
5350
|
+
// Reporting `considered` here is what distinguishes "nothing to do" from "walked away
|
|
5351
|
+
// from 59 rows because the lock was busy".
|
|
5352
|
+
considered,
|
|
5353
|
+
unchanged: 0,
|
|
5354
|
+
capped: 0
|
|
5293
5355
|
});
|
|
5294
5356
|
}
|
|
5295
5357
|
});
|
|
@@ -5297,31 +5359,54 @@ var init_memory_renderer = __esm({
|
|
|
5297
5359
|
// src/memory-render-candidates.ts
|
|
5298
5360
|
var memory_render_candidates_exports = {};
|
|
5299
5361
|
__export(memory_render_candidates_exports, {
|
|
5362
|
+
CandidateAccountingLeak: () => CandidateAccountingLeak,
|
|
5363
|
+
EXCLUSION_MEMORY_FILE_REINGEST: () => EXCLUSION_MEMORY_FILE_REINGEST,
|
|
5300
5364
|
RENDERABLE_MEMORY_TYPES: () => RENDERABLE_MEMORY_TYPES,
|
|
5301
5365
|
loadRenderCandidates: () => loadRenderCandidates
|
|
5302
5366
|
});
|
|
5303
5367
|
function loadRenderCandidates(db) {
|
|
5304
5368
|
const cfg = resolveMemoryFilesConfig();
|
|
5305
5369
|
const typePlaceholders = RENDERABLE_MEMORY_TYPES.map(() => "?").join(", ");
|
|
5370
|
+
const WHERE = `WHERE importance >= ?
|
|
5371
|
+
AND COALESCE(expired_at_epoch, 0) = 0
|
|
5372
|
+
AND type IN (${typePlaceholders})`;
|
|
5373
|
+
const { population } = db.prepare(`SELECT COUNT(*) AS population FROM observations ${WHERE}`).get(cfg.renderMinImportance, ...RENDERABLE_MEMORY_TYPES);
|
|
5306
5374
|
const rows = db.prepare(
|
|
5307
5375
|
`SELECT id, title, detail, importance, COALESCE(origin, 'local') AS origin
|
|
5308
5376
|
FROM observations
|
|
5309
|
-
|
|
5310
|
-
AND COALESCE(expired_at_epoch, 0) = 0
|
|
5311
|
-
AND type IN (${typePlaceholders})
|
|
5377
|
+
${WHERE}
|
|
5312
5378
|
ORDER BY importance DESC, created_at_epoch DESC
|
|
5313
|
-
LIMIT
|
|
5379
|
+
LIMIT ${CANDIDATE_WINDOW}`
|
|
5314
5380
|
).all(cfg.renderMinImportance, ...RENDERABLE_MEMORY_TYPES);
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5381
|
+
const reingest = rows.filter((r) => r.title.startsWith("[memory-file]"));
|
|
5382
|
+
const kept = rows.filter((r) => !r.title.startsWith("[memory-file]"));
|
|
5383
|
+
const excluded = reingest.length ? [{ reason: EXCLUSION_MEMORY_FILE_REINGEST, count: reingest.length }] : [];
|
|
5384
|
+
const ledger = {
|
|
5385
|
+
population,
|
|
5386
|
+
windowed: rows.length,
|
|
5387
|
+
truncatedByWindow: Math.max(0, population - rows.length),
|
|
5388
|
+
excluded,
|
|
5389
|
+
returned: kept.length
|
|
5390
|
+
};
|
|
5391
|
+
const accounted = ledger.returned + ledger.truncatedByWindow + excluded.reduce((n, e) => n + e.count, 0);
|
|
5392
|
+
if (accounted !== population) {
|
|
5393
|
+
throw new CandidateAccountingLeak(
|
|
5394
|
+
`candidate ledger does not balance: population=${population} but accounted=${accounted} (returned=${ledger.returned} truncated=${ledger.truncatedByWindow} excluded=${JSON.stringify(excluded)}). A row was dropped without a bucket.`
|
|
5395
|
+
);
|
|
5396
|
+
}
|
|
5397
|
+
return {
|
|
5398
|
+
ledger,
|
|
5399
|
+
candidates: kept.map((r) => ({
|
|
5400
|
+
observationId: r.id,
|
|
5401
|
+
name: r.title,
|
|
5402
|
+
title: r.title,
|
|
5403
|
+
body: r.detail ?? "",
|
|
5404
|
+
importance: r.importance,
|
|
5405
|
+
origin: r.origin
|
|
5406
|
+
}))
|
|
5407
|
+
};
|
|
5323
5408
|
}
|
|
5324
|
-
var RENDERABLE_MEMORY_TYPES;
|
|
5409
|
+
var RENDERABLE_MEMORY_TYPES, CANDIDATE_WINDOW, EXCLUSION_MEMORY_FILE_REINGEST, CandidateAccountingLeak;
|
|
5325
5410
|
var init_memory_render_candidates = __esm({
|
|
5326
5411
|
"src/memory-render-candidates.ts"() {
|
|
5327
5412
|
"use strict";
|
|
@@ -5332,6 +5417,10 @@ var init_memory_render_candidates = __esm({
|
|
|
5332
5417
|
"incident_near_miss",
|
|
5333
5418
|
"cr_violation"
|
|
5334
5419
|
];
|
|
5420
|
+
CANDIDATE_WINDOW = 50;
|
|
5421
|
+
EXCLUSION_MEMORY_FILE_REINGEST = "memory_file_reingest";
|
|
5422
|
+
CandidateAccountingLeak = class extends Error {
|
|
5423
|
+
};
|
|
5335
5424
|
}
|
|
5336
5425
|
});
|
|
5337
5426
|
|
|
@@ -14106,6 +14195,20 @@ function checkWindows(pid) {
|
|
|
14106
14195
|
|
|
14107
14196
|
// src/hooks/session-start.ts
|
|
14108
14197
|
init_hook_failure_signal();
|
|
14198
|
+
|
|
14199
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
14200
|
+
import { pathToFileURL } from "url";
|
|
14201
|
+
function isDirectInvocation(moduleUrl) {
|
|
14202
|
+
const entry = process.argv[1];
|
|
14203
|
+
if (!entry) return false;
|
|
14204
|
+
try {
|
|
14205
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
14206
|
+
} catch {
|
|
14207
|
+
return false;
|
|
14208
|
+
}
|
|
14209
|
+
}
|
|
14210
|
+
|
|
14211
|
+
// src/hooks/session-start.ts
|
|
14109
14212
|
async function main() {
|
|
14110
14213
|
try {
|
|
14111
14214
|
const input = await readStdin();
|
|
@@ -14149,7 +14252,8 @@ Session memory, code intelligence, and governance are now active.
|
|
|
14149
14252
|
if (memoryDir) {
|
|
14150
14253
|
const { renderMemoryFiles: renderMemoryFiles2 } = await Promise.resolve().then(() => (init_memory_renderer(), memory_renderer_exports));
|
|
14151
14254
|
const { loadRenderCandidates: loadRenderCandidates2 } = await Promise.resolve().then(() => (init_memory_render_candidates(), memory_render_candidates_exports));
|
|
14152
|
-
|
|
14255
|
+
const { candidates } = loadRenderCandidates2(db);
|
|
14256
|
+
renderMemoryFiles2(db, candidates, { memoryDir });
|
|
14153
14257
|
}
|
|
14154
14258
|
} catch (_renderErr) {
|
|
14155
14259
|
}
|
|
@@ -14463,7 +14567,9 @@ function formatAge(ms) {
|
|
|
14463
14567
|
const hr = Math.round(min / 60);
|
|
14464
14568
|
return `${hr}h`;
|
|
14465
14569
|
}
|
|
14466
|
-
|
|
14570
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
14571
|
+
main();
|
|
14572
|
+
}
|
|
14467
14573
|
/*! Bundled license information:
|
|
14468
14574
|
|
|
14469
14575
|
is-extglob/index.js:
|