@massu/core 2.4.0 → 2.5.1

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.
@@ -1339,6 +1339,16 @@ var init_memory_vector = __esm({
1339
1339
  }
1340
1340
  });
1341
1341
 
1342
+ // src/lib/timestamps.ts
1343
+ function nowIso() {
1344
+ return (/* @__PURE__ */ new Date()).toISOString();
1345
+ }
1346
+ var init_timestamps = __esm({
1347
+ "src/lib/timestamps.ts"() {
1348
+ "use strict";
1349
+ }
1350
+ });
1351
+
1342
1352
  // src/db-backup.ts
1343
1353
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1344
1354
  import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
@@ -3599,8 +3609,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3599
3609
  }
3600
3610
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3601
3611
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3612
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3613
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3604
3614
  `).run(
3605
3615
  sessionId,
3606
3616
  turnNumber,
@@ -3609,14 +3619,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3609
3619
  toolCallsJson,
3610
3620
  toolCallCount,
3611
3621
  promptTokens,
3612
- responseTokens
3622
+ responseTokens,
3623
+ nowIso()
3613
3624
  );
3614
3625
  return Number(result.lastInsertRowid);
3615
3626
  }
3616
3627
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3617
3628
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3629
+ 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)
3630
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3620
3631
  `).run(
3621
3632
  sessionId,
3622
3633
  turnNumber,
@@ -3625,7 +3636,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3625
3636
  inputSize,
3626
3637
  outputSize,
3627
3638
  success ? 1 : 0,
3628
- filesInvolved ? JSON.stringify(filesInvolved) : null
3639
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3640
+ nowIso()
3629
3641
  );
3630
3642
  }
3631
3643
  function getLastProcessedLine(db, sessionId) {
@@ -3884,6 +3896,7 @@ var init_memory_db = __esm({
3884
3896
  init_db_driver();
3885
3897
  init_config();
3886
3898
  init_memory_vector();
3899
+ init_timestamps();
3887
3900
  init_db_backup();
3888
3901
  init_rule_delivery();
3889
3902
  init_rule_candidate_store();
@@ -3980,12 +3993,43 @@ import { tmpdir as tmpdir2 } from "os";
3980
3993
  import { join as join6 } from "path";
3981
3994
 
3982
3995
  // src/hooks/lib/write-hook-message.ts
3983
- function writeHookMessage(message) {
3984
- process.stdout.write(JSON.stringify({ message }) + "\n");
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
+ );
3985
4028
  }
3986
4029
 
3987
4030
  // src/hooks/auto-learning-pipeline.ts
3988
4031
  init_hook_failure_signal();
4032
+ var HOOK_EVENT = "Stop";
3989
4033
  var MAX_FULL_DIFF_BYTES = 2 * 1024 * 1024;
3990
4034
  function getSessionFlagPath(sessionId) {
3991
4035
  return join6(tmpdir2(), "massu-auto-learning", `fixes-${sessionId.slice(0, 12)}.jsonl`);
@@ -4104,7 +4148,7 @@ async function main() {
4104
4148
  lines.push("");
4105
4149
  lines.push("============================================================================");
4106
4150
  lines.push("");
4107
- writeHookMessage(lines.join("\n"));
4151
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4108
4152
  cleanup(flagPath);
4109
4153
  } catch (err) {
4110
4154
  recordHookFailure("auto-learning-pipeline", err);
@@ -1339,6 +1339,16 @@ var init_memory_vector = __esm({
1339
1339
  }
1340
1340
  });
1341
1341
 
1342
+ // src/lib/timestamps.ts
1343
+ function nowIso() {
1344
+ return (/* @__PURE__ */ new Date()).toISOString();
1345
+ }
1346
+ var init_timestamps = __esm({
1347
+ "src/lib/timestamps.ts"() {
1348
+ "use strict";
1349
+ }
1350
+ });
1351
+
1342
1352
  // src/db-backup.ts
1343
1353
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1344
1354
  import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
@@ -3670,8 +3680,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3670
3680
  }
3671
3681
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3672
3682
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3683
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3684
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3675
3685
  `).run(
3676
3686
  sessionId,
3677
3687
  turnNumber,
@@ -3680,14 +3690,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3680
3690
  toolCallsJson,
3681
3691
  toolCallCount,
3682
3692
  promptTokens,
3683
- responseTokens
3693
+ responseTokens,
3694
+ nowIso()
3684
3695
  );
3685
3696
  return Number(result.lastInsertRowid);
3686
3697
  }
3687
3698
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3688
3699
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3700
+ 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)
3701
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3691
3702
  `).run(
3692
3703
  sessionId,
3693
3704
  turnNumber,
@@ -3696,7 +3707,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3696
3707
  inputSize,
3697
3708
  outputSize,
3698
3709
  success ? 1 : 0,
3699
- filesInvolved ? JSON.stringify(filesInvolved) : null
3710
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3711
+ nowIso()
3700
3712
  );
3701
3713
  }
3702
3714
  function getLastProcessedLine(db, sessionId) {
@@ -3955,6 +3967,7 @@ var init_memory_db = __esm({
3955
3967
  init_db_driver();
3956
3968
  init_config();
3957
3969
  init_memory_vector();
3970
+ init_timestamps();
3958
3971
  init_db_backup();
3959
3972
  init_rule_delivery();
3960
3973
  init_rule_candidate_store();
@@ -3980,12 +3993,43 @@ import { tmpdir as tmpdir2 } from "os";
3980
3993
  import { join as join6, basename as basename3 } from "path";
3981
3994
 
3982
3995
  // src/hooks/lib/write-hook-message.ts
3983
- function writeHookMessage(message) {
3984
- process.stdout.write(JSON.stringify({ message }) + "\n");
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
+ );
3985
4028
  }
3986
4029
 
3987
4030
  // src/hooks/classify-failure.ts
3988
4031
  init_hook_failure_signal();
4032
+ var HOOK_EVENT = "PostToolUse";
3989
4033
  var BUG_FIX_INDICATORS = [
3990
4034
  /\b(catch|error|throw|fail|fix|bug|broken|missing|crash|wrong|typo|incorrect)\b/i
3991
4035
  ];
@@ -4127,7 +4171,7 @@ function outputKnownPattern(match) {
4127
4171
  lines.push(` Covered by: ${match.rules.join(", ")}`);
4128
4172
  }
4129
4173
  lines.push(" No new rules needed. Reference existing incident if logging.");
4130
- writeHookMessage(lines.join("\n"));
4174
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4131
4175
  }
4132
4176
  function outputSimilarPattern(match) {
4133
4177
  const lines = [];
@@ -4137,7 +4181,7 @@ function outputSimilarPattern(match) {
4137
4181
  lines.push(` Check if existing rules cover this case: ${match.rules.join(", ")}`);
4138
4182
  }
4139
4183
  lines.push(" If genuinely new: create incident + prevention rule + enforcement.");
4140
- writeHookMessage(lines.join("\n"));
4184
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4141
4185
  }
4142
4186
  function outputNewPattern(fileName, match) {
4143
4187
  const lines = [];
@@ -4151,7 +4195,7 @@ function outputNewPattern(fileName, match) {
4151
4195
  lines.push(" 1. INCIDENT REPORT");
4152
4196
  lines.push(" 2. PREVENTION RULE (if new failure pattern)");
4153
4197
  lines.push(" 3. ENFORCEMENT (hook or static check)");
4154
- writeHookMessage(lines.join("\n"));
4198
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4155
4199
  }
4156
4200
  function readStdin() {
4157
4201
  return new Promise((resolve5) => {
@@ -1339,6 +1339,16 @@ var init_memory_vector = __esm({
1339
1339
  }
1340
1340
  });
1341
1341
 
1342
+ // src/lib/timestamps.ts
1343
+ function nowIso() {
1344
+ return (/* @__PURE__ */ new Date()).toISOString();
1345
+ }
1346
+ var init_timestamps = __esm({
1347
+ "src/lib/timestamps.ts"() {
1348
+ "use strict";
1349
+ }
1350
+ });
1351
+
1342
1352
  // src/db-backup.ts
1343
1353
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1344
1354
  import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
@@ -3670,8 +3680,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3670
3680
  }
3671
3681
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3672
3682
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3683
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3684
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3675
3685
  `).run(
3676
3686
  sessionId,
3677
3687
  turnNumber,
@@ -3680,14 +3690,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3680
3690
  toolCallsJson,
3681
3691
  toolCallCount,
3682
3692
  promptTokens,
3683
- responseTokens
3693
+ responseTokens,
3694
+ nowIso()
3684
3695
  );
3685
3696
  return Number(result.lastInsertRowid);
3686
3697
  }
3687
3698
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3688
3699
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3700
+ 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)
3701
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3691
3702
  `).run(
3692
3703
  sessionId,
3693
3704
  turnNumber,
@@ -3696,7 +3707,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3696
3707
  inputSize,
3697
3708
  outputSize,
3698
3709
  success ? 1 : 0,
3699
- filesInvolved ? JSON.stringify(filesInvolved) : null
3710
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3711
+ nowIso()
3700
3712
  );
3701
3713
  }
3702
3714
  function getLastProcessedLine(db, sessionId) {
@@ -3955,6 +3967,7 @@ var init_memory_db = __esm({
3955
3967
  init_db_driver();
3956
3968
  init_config();
3957
3969
  init_memory_vector();
3970
+ init_timestamps();
3958
3971
  init_db_backup();
3959
3972
  init_rule_delivery();
3960
3973
  init_rule_candidate_store();
@@ -4072,6 +4085,7 @@ function toolResponseText(raw) {
4072
4085
 
4073
4086
  // src/hooks/cost-tracker.ts
4074
4087
  init_hook_failure_signal();
4088
+ init_timestamps();
4075
4089
  var CHARS_PER_TOKEN = 4;
4076
4090
  function estimateTokens(text) {
4077
4091
  return Math.ceil(text.length / CHARS_PER_TOKEN);
@@ -4087,9 +4101,9 @@ async function main() {
4087
4101
  const db = getMemoryDb();
4088
4102
  try {
4089
4103
  db.prepare(`
4090
- INSERT INTO tool_cost_events (session_id, tool_name, estimated_input_tokens, estimated_output_tokens, model)
4091
- VALUES (?, ?, ?, ?, ?)
4092
- `).run(session_id, tool_name, estimatedInputTokens, estimatedOutputTokens, "");
4104
+ INSERT INTO tool_cost_events (session_id, tool_name, estimated_input_tokens, estimated_output_tokens, model, created_at)
4105
+ VALUES (?, ?, ?, ?, ?, ?)
4106
+ `).run(session_id, tool_name, estimatedInputTokens, estimatedOutputTokens, "", nowIso());
4093
4107
  } finally {
4094
4108
  db.close();
4095
4109
  }
@@ -1345,6 +1345,16 @@ var init_memory_vector = __esm({
1345
1345
  }
1346
1346
  });
1347
1347
 
1348
+ // src/lib/timestamps.ts
1349
+ function nowIso() {
1350
+ return (/* @__PURE__ */ new Date()).toISOString();
1351
+ }
1352
+ var init_timestamps = __esm({
1353
+ "src/lib/timestamps.ts"() {
1354
+ "use strict";
1355
+ }
1356
+ });
1357
+
1348
1358
  // src/db-backup.ts
1349
1359
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1350
1360
  import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
@@ -3605,8 +3615,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3605
3615
  }
3606
3616
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3607
3617
  const result = db.prepare(`
3608
- INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
3609
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3618
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3619
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3610
3620
  `).run(
3611
3621
  sessionId,
3612
3622
  turnNumber,
@@ -3615,14 +3625,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3615
3625
  toolCallsJson,
3616
3626
  toolCallCount,
3617
3627
  promptTokens,
3618
- responseTokens
3628
+ responseTokens,
3629
+ nowIso()
3619
3630
  );
3620
3631
  return Number(result.lastInsertRowid);
3621
3632
  }
3622
3633
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3623
3634
  db.prepare(`
3624
- INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
3625
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3635
+ 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)
3636
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3626
3637
  `).run(
3627
3638
  sessionId,
3628
3639
  turnNumber,
@@ -3631,7 +3642,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3631
3642
  inputSize,
3632
3643
  outputSize,
3633
3644
  success ? 1 : 0,
3634
- filesInvolved ? JSON.stringify(filesInvolved) : null
3645
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3646
+ nowIso()
3635
3647
  );
3636
3648
  }
3637
3649
  function getLastProcessedLine(db, sessionId) {
@@ -3890,6 +3902,7 @@ var init_memory_db = __esm({
3890
3902
  init_db_driver();
3891
3903
  init_config();
3892
3904
  init_memory_vector();
3905
+ init_timestamps();
3893
3906
  init_db_backup();
3894
3907
  init_rule_delivery();
3895
3908
  init_rule_candidate_store();
@@ -3986,12 +3999,43 @@ import { tmpdir as tmpdir2 } from "os";
3986
3999
  import { join as join6 } from "path";
3987
4000
 
3988
4001
  // src/hooks/lib/write-hook-message.ts
3989
- function writeHookMessage(message) {
3990
- process.stdout.write(JSON.stringify({ message }) + "\n");
4002
+ var HOOK_EVENTS = [
4003
+ "UserPromptSubmit",
4004
+ "UserPromptExpansion",
4005
+ "SessionStart",
4006
+ "PreToolUse",
4007
+ "PostToolUse",
4008
+ "Stop",
4009
+ "SubagentStop",
4010
+ "PreCompact"
4011
+ ];
4012
+ var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
4013
+ function isHookEvent(value) {
4014
+ return typeof value === "string" && HOOK_EVENTS.includes(value);
4015
+ }
4016
+ function writeHookContext(hookEventName, message) {
4017
+ if (!isHookEvent(hookEventName)) {
4018
+ process.stderr.write(
4019
+ `[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
4020
+ `
4021
+ );
4022
+ return;
4023
+ }
4024
+ if (typeof message !== "string" || message.length === 0) return;
4025
+ let additionalContext = message;
4026
+ if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
4027
+ const marker = `
4028
+ [massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
4029
+ additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
4030
+ }
4031
+ process.stdout.write(
4032
+ JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
4033
+ );
3991
4034
  }
3992
4035
 
3993
4036
  // src/hooks/fix-detector.ts
3994
4037
  init_hook_failure_signal();
4038
+ var HOOK_EVENT = "PostToolUse";
3995
4039
  var FIX_HEURISTICS = [
3996
4040
  {
3997
4041
  name: "removed_broken_code",
@@ -4166,7 +4210,8 @@ async function main() {
4166
4210
  appendFileSync3(flagPath, JSON.stringify(signal) + "\n");
4167
4211
  const lines = readFileSync6(flagPath, "utf-8").split("\n").filter(Boolean);
4168
4212
  if (lines.length === 1) {
4169
- writeHookMessage(
4213
+ writeHookContext(
4214
+ HOOK_EVENT,
4170
4215
  `[Massu Auto-Learning] Bug fix detected in ${filePath} (signals: ${detected.join(", ")}). The auto-learning pipeline will prompt you at session end to create an incident report, derive a prevention rule, and add enforcement.`
4171
4216
  );
4172
4217
  }
@@ -1339,6 +1339,16 @@ var init_memory_vector = __esm({
1339
1339
  }
1340
1340
  });
1341
1341
 
1342
+ // src/lib/timestamps.ts
1343
+ function nowIso() {
1344
+ return (/* @__PURE__ */ new Date()).toISOString();
1345
+ }
1346
+ var init_timestamps = __esm({
1347
+ "src/lib/timestamps.ts"() {
1348
+ "use strict";
1349
+ }
1350
+ });
1351
+
1342
1352
  // src/db-backup.ts
1343
1353
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1344
1354
  import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
@@ -3670,8 +3680,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3670
3680
  }
3671
3681
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3672
3682
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3683
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3684
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3675
3685
  `).run(
3676
3686
  sessionId,
3677
3687
  turnNumber,
@@ -3680,14 +3690,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3680
3690
  toolCallsJson,
3681
3691
  toolCallCount,
3682
3692
  promptTokens,
3683
- responseTokens
3693
+ responseTokens,
3694
+ nowIso()
3684
3695
  );
3685
3696
  return Number(result.lastInsertRowid);
3686
3697
  }
3687
3698
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3688
3699
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3700
+ 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)
3701
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3691
3702
  `).run(
3692
3703
  sessionId,
3693
3704
  turnNumber,
@@ -3696,7 +3707,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3696
3707
  inputSize,
3697
3708
  outputSize,
3698
3709
  success ? 1 : 0,
3699
- filesInvolved ? JSON.stringify(filesInvolved) : null
3710
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3711
+ nowIso()
3700
3712
  );
3701
3713
  }
3702
3714
  function getLastProcessedLine(db, sessionId) {
@@ -3955,6 +3967,7 @@ var init_memory_db = __esm({
3955
3967
  init_db_driver();
3956
3968
  init_config();
3957
3969
  init_memory_vector();
3970
+ init_timestamps();
3958
3971
  init_db_backup();
3959
3972
  init_rule_delivery();
3960
3973
  init_rule_candidate_store();
@@ -3979,12 +3992,43 @@ import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync a
3979
3992
  import { basename as basename3, resolve as resolve5 } from "path";
3980
3993
 
3981
3994
  // src/hooks/lib/write-hook-message.ts
3982
- function writeHookMessage(message) {
3983
- process.stdout.write(JSON.stringify({ message }) + "\n");
3995
+ var HOOK_EVENTS = [
3996
+ "UserPromptSubmit",
3997
+ "UserPromptExpansion",
3998
+ "SessionStart",
3999
+ "PreToolUse",
4000
+ "PostToolUse",
4001
+ "Stop",
4002
+ "SubagentStop",
4003
+ "PreCompact"
4004
+ ];
4005
+ var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
4006
+ function isHookEvent(value) {
4007
+ return typeof value === "string" && HOOK_EVENTS.includes(value);
4008
+ }
4009
+ function writeHookContext(hookEventName, message) {
4010
+ if (!isHookEvent(hookEventName)) {
4011
+ process.stderr.write(
4012
+ `[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
4013
+ `
4014
+ );
4015
+ return;
4016
+ }
4017
+ if (typeof message !== "string" || message.length === 0) return;
4018
+ let additionalContext = message;
4019
+ if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
4020
+ const marker = `
4021
+ [massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
4022
+ additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
4023
+ }
4024
+ process.stdout.write(
4025
+ JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
4026
+ );
3984
4027
  }
3985
4028
 
3986
4029
  // src/hooks/incident-pipeline.ts
3987
4030
  init_hook_failure_signal();
4031
+ var HOOK_EVENT = "PostToolUse";
3988
4032
  async function main() {
3989
4033
  try {
3990
4034
  const input = await readStdin();
@@ -4067,7 +4111,7 @@ async function main() {
4067
4111
  lines.push(" This step is MANDATORY per the auto-learning pipeline.");
4068
4112
  lines.push("============================================================================");
4069
4113
  lines.push("");
4070
- writeHookMessage(lines.join("\n"));
4114
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4071
4115
  try {
4072
4116
  const taxonomyConfig = config.autoLearning?.failureClassification;
4073
4117
  if (taxonomyConfig?.enabled !== false) {