@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.
@@ -703,6 +703,13 @@ var init_config = __esm({
703
703
  });
704
704
  LanguageFrameworkEntrySchema = z2.object({
705
705
  framework: z2.string().optional(),
706
+ // `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
707
+ // by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
708
+ // `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
709
+ // derives the index builders' candidate set from it, so it is typed here rather than
710
+ // read as an unknown: a consumer that has to shape-check its own SoT is one branch away
711
+ // from silently contributing `undefined` to a path predicate.
712
+ source_dirs: z2.array(z2.string()).optional(),
706
713
  test_framework: z2.string().optional(),
707
714
  test: z2.string().optional(),
708
715
  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";
@@ -3605,8 +3622,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3605
3622
  }
3606
3623
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3607
3624
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3625
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3626
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3610
3627
  `).run(
3611
3628
  sessionId,
3612
3629
  turnNumber,
@@ -3615,14 +3632,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3615
3632
  toolCallsJson,
3616
3633
  toolCallCount,
3617
3634
  promptTokens,
3618
- responseTokens
3635
+ responseTokens,
3636
+ nowIso()
3619
3637
  );
3620
3638
  return Number(result.lastInsertRowid);
3621
3639
  }
3622
3640
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3623
3641
  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 (?, ?, ?, ?, ?, ?, ?, ?)
3642
+ 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)
3643
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3626
3644
  `).run(
3627
3645
  sessionId,
3628
3646
  turnNumber,
@@ -3631,7 +3649,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3631
3649
  inputSize,
3632
3650
  outputSize,
3633
3651
  success ? 1 : 0,
3634
- filesInvolved ? JSON.stringify(filesInvolved) : null
3652
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3653
+ nowIso()
3635
3654
  );
3636
3655
  }
3637
3656
  function getLastProcessedLine(db, sessionId) {
@@ -3890,6 +3909,7 @@ var init_memory_db = __esm({
3890
3909
  init_db_driver();
3891
3910
  init_config();
3892
3911
  init_memory_vector();
3912
+ init_timestamps();
3893
3913
  init_db_backup();
3894
3914
  init_rule_delivery();
3895
3915
  init_rule_candidate_store();
@@ -3986,12 +4006,65 @@ import { tmpdir as tmpdir2 } from "os";
3986
4006
  import { join as join6 } from "path";
3987
4007
 
3988
4008
  // src/hooks/lib/write-hook-message.ts
3989
- function writeHookMessage(message) {
3990
- process.stdout.write(JSON.stringify({ message }) + "\n");
4009
+ var HOOK_EVENTS = [
4010
+ "UserPromptSubmit",
4011
+ "UserPromptExpansion",
4012
+ "SessionStart",
4013
+ "PreToolUse",
4014
+ "PostToolUse",
4015
+ "Stop",
4016
+ "SubagentStop",
4017
+ "PreCompact"
4018
+ ];
4019
+ var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
4020
+ function isHookEvent(value) {
4021
+ return typeof value === "string" && HOOK_EVENTS.includes(value);
4022
+ }
4023
+ function writeHookContext(hookEventName, message) {
4024
+ if (!isHookEvent(hookEventName)) {
4025
+ process.stderr.write(
4026
+ `[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
4027
+ `
4028
+ );
4029
+ return;
4030
+ }
4031
+ if (typeof message !== "string" || message.length === 0) return;
4032
+ let additionalContext = message;
4033
+ if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
4034
+ const marker = `
4035
+ [massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
4036
+ additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
4037
+ }
4038
+ process.stdout.write(
4039
+ JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
4040
+ );
3991
4041
  }
3992
4042
 
3993
4043
  // src/hooks/fix-detector.ts
3994
4044
  init_hook_failure_signal();
4045
+
4046
+ // src/hooks/lib/is-direct-invocation.ts
4047
+ import { pathToFileURL } from "url";
4048
+ function isDirectInvocation(moduleUrl) {
4049
+ const entry = process.argv[1];
4050
+ if (!entry) return false;
4051
+ try {
4052
+ return moduleUrl === pathToFileURL(entry).href;
4053
+ } catch {
4054
+ return false;
4055
+ }
4056
+ }
4057
+
4058
+ // src/hooks/fix-detector.ts
4059
+ var HOOK_EVENT = "PostToolUse";
4060
+ var TEST_OR_FIXTURE_PATH_PATTERNS = [
4061
+ /(^|\/)(__tests__|__fixtures__|tests?|fixtures?|spec)\//,
4062
+ /\.(test|spec)\.[cm]?[jt]sx?$/,
4063
+ /(^|\/)(test_[^/]+|[^/]+_test)\.py$/
4064
+ ];
4065
+ function isTestOrFixturePath(filePath) {
4066
+ return TEST_OR_FIXTURE_PATH_PATTERNS.some((re) => re.test(filePath));
4067
+ }
3995
4068
  var FIX_HEURISTICS = [
3996
4069
  {
3997
4070
  name: "removed_broken_code",
@@ -4118,6 +4191,10 @@ async function main() {
4118
4191
  process.exit(0);
4119
4192
  return;
4120
4193
  }
4194
+ if (isTestOrFixturePath(filePath)) {
4195
+ process.exit(0);
4196
+ return;
4197
+ }
4121
4198
  if (config.autoLearning?.enabled === false || config.autoLearning?.fixDetection?.enabled === false) {
4122
4199
  process.exit(0);
4123
4200
  return;
@@ -4166,7 +4243,8 @@ async function main() {
4166
4243
  appendFileSync3(flagPath, JSON.stringify(signal) + "\n");
4167
4244
  const lines = readFileSync6(flagPath, "utf-8").split("\n").filter(Boolean);
4168
4245
  if (lines.length === 1) {
4169
- writeHookMessage(
4246
+ writeHookContext(
4247
+ HOOK_EVENT,
4170
4248
  `[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
4249
  );
4172
4250
  }
@@ -4186,4 +4264,10 @@ function readStdin() {
4186
4264
  setTimeout(() => resolve5(data), 3e3);
4187
4265
  });
4188
4266
  }
4189
- main();
4267
+ if (isDirectInvocation(import.meta.url)) {
4268
+ main();
4269
+ }
4270
+ export {
4271
+ TEST_OR_FIXTURE_PATH_PATTERNS,
4272
+ isTestOrFixturePath
4273
+ };
@@ -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";
@@ -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();
@@ -3979,12 +3999,57 @@ import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync a
3979
3999
  import { basename as basename3, resolve as resolve5 } from "path";
3980
4000
 
3981
4001
  // src/hooks/lib/write-hook-message.ts
3982
- function writeHookMessage(message) {
3983
- 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
+ );
3984
4034
  }
3985
4035
 
3986
4036
  // src/hooks/incident-pipeline.ts
3987
4037
  init_hook_failure_signal();
4038
+
4039
+ // src/hooks/lib/is-direct-invocation.ts
4040
+ import { pathToFileURL } from "url";
4041
+ function isDirectInvocation(moduleUrl) {
4042
+ const entry = process.argv[1];
4043
+ if (!entry) return false;
4044
+ try {
4045
+ return moduleUrl === pathToFileURL(entry).href;
4046
+ } catch {
4047
+ return false;
4048
+ }
4049
+ }
4050
+
4051
+ // src/hooks/incident-pipeline.ts
4052
+ var HOOK_EVENT = "PostToolUse";
3988
4053
  async function main() {
3989
4054
  try {
3990
4055
  const input = await readStdin();
@@ -4067,7 +4132,7 @@ async function main() {
4067
4132
  lines.push(" This step is MANDATORY per the auto-learning pipeline.");
4068
4133
  lines.push("============================================================================");
4069
4134
  lines.push("");
4070
- writeHookMessage(lines.join("\n"));
4135
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4071
4136
  try {
4072
4137
  const taxonomyConfig = config.autoLearning?.failureClassification;
4073
4138
  if (taxonomyConfig?.enabled !== false) {
@@ -4111,4 +4176,6 @@ function readStdin() {
4111
4176
  setTimeout(() => resolve6(data), 3e3);
4112
4177
  });
4113
4178
  }
4114
- main();
4179
+ if (isDirectInvocation(import.meta.url)) {
4180
+ main();
4181
+ }
@@ -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
- function writeHookMessage(message) {
3977
- 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
+ );
3978
4028
  }
3979
4029
 
3980
4030
  // src/hooks/intent-suggester.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/intent-suggester.ts
4046
+ var HOOK_EVENT = "UserPromptSubmit";
3982
4047
  var COMMAND_MAPPINGS = [
3983
4048
  {
3984
4049
  keywords: ["test", "failing"],
@@ -4056,7 +4121,7 @@ async function main() {
4056
4121
  process.exit(0);
4057
4122
  return;
4058
4123
  }
4059
- writeHookMessage(`Tip: Use ${match.command} to ${match.description}.`);
4124
+ writeHookContext(HOOK_EVENT, `Tip: Use ${match.command} to ${match.description}.`);
4060
4125
  } catch (err) {
4061
4126
  recordHookFailure("intent-suggester", err);
4062
4127
  }
@@ -4073,4 +4138,6 @@ function readStdin() {
4073
4138
  setTimeout(() => resolve5(data), 3e3);
4074
4139
  });
4075
4140
  }
4076
- main();
4141
+ if (isDirectInvocation(import.meta.url)) {
4142
+ main();
4143
+ }
@@ -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";
@@ -3706,8 +3723,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3706
3723
  }
3707
3724
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3708
3725
  const result = db.prepare(`
3709
- INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
3710
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3726
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3727
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3711
3728
  `).run(
3712
3729
  sessionId,
3713
3730
  turnNumber,
@@ -3716,14 +3733,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3716
3733
  toolCallsJson,
3717
3734
  toolCallCount,
3718
3735
  promptTokens,
3719
- responseTokens
3736
+ responseTokens,
3737
+ nowIso()
3720
3738
  );
3721
3739
  return Number(result.lastInsertRowid);
3722
3740
  }
3723
3741
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3724
3742
  db.prepare(`
3725
- INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
3726
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3743
+ 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)
3744
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3727
3745
  `).run(
3728
3746
  sessionId,
3729
3747
  turnNumber,
@@ -3732,7 +3750,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3732
3750
  inputSize,
3733
3751
  outputSize,
3734
3752
  success ? 1 : 0,
3735
- filesInvolved ? JSON.stringify(filesInvolved) : null
3753
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3754
+ nowIso()
3736
3755
  );
3737
3756
  }
3738
3757
  function getLastProcessedLine(db, sessionId) {
@@ -3991,6 +4010,7 @@ var init_memory_db = __esm({
3991
4010
  init_db_driver();
3992
4011
  init_config();
3993
4012
  init_memory_vector();
4013
+ init_timestamps();
3994
4014
  init_db_backup();
3995
4015
  init_rule_delivery();
3996
4016
  init_rule_candidate_store();
@@ -4520,14 +4540,59 @@ function pendingPointer(db) {
4520
4540
  init_memory_embedder();
4521
4541
 
4522
4542
  // src/hooks/lib/write-hook-message.ts
4523
- function writeHookMessage(message) {
4524
- process.stdout.write(JSON.stringify({ message }) + "\n");
4543
+ var HOOK_EVENTS = [
4544
+ "UserPromptSubmit",
4545
+ "UserPromptExpansion",
4546
+ "SessionStart",
4547
+ "PreToolUse",
4548
+ "PostToolUse",
4549
+ "Stop",
4550
+ "SubagentStop",
4551
+ "PreCompact"
4552
+ ];
4553
+ var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
4554
+ function isHookEvent(value) {
4555
+ return typeof value === "string" && HOOK_EVENTS.includes(value);
4556
+ }
4557
+ function writeHookContext(hookEventName, message) {
4558
+ if (!isHookEvent(hookEventName)) {
4559
+ process.stderr.write(
4560
+ `[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
4561
+ `
4562
+ );
4563
+ return;
4564
+ }
4565
+ if (typeof message !== "string" || message.length === 0) return;
4566
+ let additionalContext = message;
4567
+ if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
4568
+ const marker = `
4569
+ [massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
4570
+ additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
4571
+ }
4572
+ process.stdout.write(
4573
+ JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
4574
+ );
4525
4575
  }
4526
4576
 
4527
4577
  // src/hooks/memory-recall.ts
4528
4578
  init_db_driver();
4529
4579
  init_hook_failure_signal();
4530
4580
  import { existsSync as existsSync9 } from "fs";
4581
+
4582
+ // src/hooks/lib/is-direct-invocation.ts
4583
+ import { pathToFileURL } from "url";
4584
+ function isDirectInvocation(moduleUrl) {
4585
+ const entry = process.argv[1];
4586
+ if (!entry) return false;
4587
+ try {
4588
+ return moduleUrl === pathToFileURL(entry).href;
4589
+ } catch {
4590
+ return false;
4591
+ }
4592
+ }
4593
+
4594
+ // src/hooks/memory-recall.ts
4595
+ var HOOK_EVENT = "UserPromptSubmit";
4531
4596
  var DEFAULTS = {
4532
4597
  enabled: true,
4533
4598
  maxTokens: 1200,
@@ -4581,7 +4646,7 @@ async function main() {
4581
4646
  cfg.timeoutMs
4582
4647
  );
4583
4648
  if (block && block.trim()) {
4584
- writeHookMessage(block);
4649
+ writeHookContext(HOOK_EVENT, block);
4585
4650
  }
4586
4651
  } catch {
4587
4652
  } finally {
@@ -4679,4 +4744,6 @@ function readStdin() {
4679
4744
  setTimeout(() => resolve5(data), 3e3);
4680
4745
  });
4681
4746
  }
4682
- main();
4747
+ if (isDirectInvocation(import.meta.url)) {
4748
+ main();
4749
+ }