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