@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.
@@ -4,6 +4,12 @@ var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
+ }) : x)(function(x) {
10
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
+ throw Error('Dynamic require of "' + x + '" is not supported');
12
+ });
7
13
  var __esm = (fn, res, err) => function __init() {
8
14
  if (err) throw err[0];
9
15
  try {
@@ -697,6 +703,13 @@ var init_config = __esm({
697
703
  });
698
704
  LanguageFrameworkEntrySchema = z2.object({
699
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(),
700
713
  test_framework: z2.string().optional(),
701
714
  test: z2.string().optional(),
702
715
  runtime: z2.string().optional(),
@@ -1339,6 +1352,16 @@ var init_memory_vector = __esm({
1339
1352
  }
1340
1353
  });
1341
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
+
1342
1365
  // src/db-backup.ts
1343
1366
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1344
1367
  import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
@@ -1804,8 +1827,8 @@ function resolveModelDir() {
1804
1827
  function resolveWasmDir() {
1805
1828
  try {
1806
1829
  const req3 = createRequire3(import.meta.url);
1807
- const main2 = req3.resolve(ORT_PKG);
1808
- return dirname6(main2);
1830
+ const main3 = req3.resolve(ORT_PKG);
1831
+ return dirname6(main3);
1809
1832
  } catch {
1810
1833
  return null;
1811
1834
  }
@@ -3670,8 +3693,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3670
3693
  }
3671
3694
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3672
3695
  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 (?, ?, ?, ?, ?, ?, ?, ?)
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 (?, ?, ?, ?, ?, ?, ?, ?, ?)
3675
3698
  `).run(
3676
3699
  sessionId,
3677
3700
  turnNumber,
@@ -3680,14 +3703,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3680
3703
  toolCallsJson,
3681
3704
  toolCallCount,
3682
3705
  promptTokens,
3683
- responseTokens
3706
+ responseTokens,
3707
+ nowIso()
3684
3708
  );
3685
3709
  return Number(result.lastInsertRowid);
3686
3710
  }
3687
3711
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3688
3712
  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 (?, ?, ?, ?, ?, ?, ?, ?)
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 (?, ?, ?, ?, ?, ?, ?, ?, ?)
3691
3715
  `).run(
3692
3716
  sessionId,
3693
3717
  turnNumber,
@@ -3696,7 +3720,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3696
3720
  inputSize,
3697
3721
  outputSize,
3698
3722
  success ? 1 : 0,
3699
- filesInvolved ? JSON.stringify(filesInvolved) : null
3723
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3724
+ nowIso()
3700
3725
  );
3701
3726
  }
3702
3727
  function getLastProcessedLine(db, sessionId) {
@@ -3955,6 +3980,7 @@ var init_memory_db = __esm({
3955
3980
  init_db_driver();
3956
3981
  init_config();
3957
3982
  init_memory_vector();
3983
+ init_timestamps();
3958
3984
  init_db_backup();
3959
3985
  init_rule_delivery();
3960
3986
  init_rule_candidate_store();
@@ -3975,17 +4001,283 @@ var init_memory_db = __esm({
3975
4001
  // src/hooks/classify-failure.ts
3976
4002
  init_config();
3977
4003
  init_memory_db();
3978
- import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
4004
+ import { existsSync as existsSync10, readFileSync as readFileSync7, readdirSync as readdirSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
4005
+ import { tmpdir as tmpdir3 } from "os";
4006
+ import { join as join7, basename as basename3 } from "path";
4007
+
4008
+ // src/hooks/fix-detector.ts
4009
+ init_config();
4010
+ import { execFileSync } from "child_process";
4011
+ import { existsSync as existsSync9, appendFileSync as appendFileSync3, mkdirSync as mkdirSync7, readFileSync as readFileSync6 } from "fs";
3979
4012
  import { tmpdir as tmpdir2 } from "os";
3980
- import { join as join6, basename as basename3 } from "path";
4013
+ import { join as join6 } from "path";
3981
4014
 
3982
4015
  // src/hooks/lib/write-hook-message.ts
3983
- function writeHookMessage(message) {
3984
- process.stdout.write(JSON.stringify({ message }) + "\n");
4016
+ var HOOK_EVENTS = [
4017
+ "UserPromptSubmit",
4018
+ "UserPromptExpansion",
4019
+ "SessionStart",
4020
+ "PreToolUse",
4021
+ "PostToolUse",
4022
+ "Stop",
4023
+ "SubagentStop",
4024
+ "PreCompact"
4025
+ ];
4026
+ var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
4027
+ function isHookEvent(value) {
4028
+ return typeof value === "string" && HOOK_EVENTS.includes(value);
4029
+ }
4030
+ function writeHookContext(hookEventName, message) {
4031
+ if (!isHookEvent(hookEventName)) {
4032
+ process.stderr.write(
4033
+ `[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
4034
+ `
4035
+ );
4036
+ return;
4037
+ }
4038
+ if (typeof message !== "string" || message.length === 0) return;
4039
+ let additionalContext = message;
4040
+ if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
4041
+ const marker = `
4042
+ [massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
4043
+ additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
4044
+ }
4045
+ process.stdout.write(
4046
+ JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
4047
+ );
4048
+ }
4049
+
4050
+ // src/hooks/fix-detector.ts
4051
+ init_hook_failure_signal();
4052
+
4053
+ // src/hooks/lib/is-direct-invocation.ts
4054
+ import { pathToFileURL } from "url";
4055
+ function isDirectInvocation(moduleUrl) {
4056
+ const entry = process.argv[1];
4057
+ if (!entry) return false;
4058
+ try {
4059
+ return moduleUrl === pathToFileURL(entry).href;
4060
+ } catch {
4061
+ return false;
4062
+ }
4063
+ }
4064
+
4065
+ // src/hooks/fix-detector.ts
4066
+ var HOOK_EVENT = "PostToolUse";
4067
+ var TEST_OR_FIXTURE_PATH_PATTERNS = [
4068
+ /(^|\/)(__tests__|__fixtures__|tests?|fixtures?|spec)\//,
4069
+ /\.(test|spec)\.[cm]?[jt]sx?$/,
4070
+ /(^|\/)(test_[^/]+|[^/]+_test)\.py$/
4071
+ ];
4072
+ function isTestOrFixturePath(filePath) {
4073
+ return TEST_OR_FIXTURE_PATH_PATTERNS.some((re) => re.test(filePath));
4074
+ }
4075
+ var FIX_HEURISTICS = [
4076
+ {
4077
+ name: "removed_broken_code",
4078
+ test: (diff) => /^-.*\b(bug|broken|wrong|incorrect|typo|crash|error|fail|miss|stale)\b/m.test(diff)
4079
+ },
4080
+ {
4081
+ name: "added_error_handling",
4082
+ test: (diff) => {
4083
+ const added = (diff.match(/^\+.*(try|except|catch|guard|if.*nil|if.*None|validate|assert|raise|throw)/gm) || []).length;
4084
+ return added > 2;
4085
+ }
4086
+ },
4087
+ {
4088
+ name: "method_name_correction",
4089
+ test: (diff) => {
4090
+ const removed = diff.match(/^-.*\.([a-z_]+)\(/m);
4091
+ const added = diff.match(/^\+.*\.([a-z_]+)\(/m);
4092
+ return !!(removed && added && removed[1] !== added[1]);
4093
+ }
4094
+ },
4095
+ {
4096
+ name: "auth_fix",
4097
+ test: (diff) => /^\+.*(token|auth|header|X-Service|Bearer|credential)/im.test(diff)
4098
+ },
4099
+ {
4100
+ name: "nil_handling_fix",
4101
+ test: (diff) => /^\+.*(= nil|= None|\.isNil|is None|!= nil|is not None|guard let|if let|optional)/m.test(diff) && /^-/m.test(diff)
4102
+ },
4103
+ {
4104
+ name: "concurrency_fix",
4105
+ test: (diff) => /^\+.*(timeout|semaphore|lock|mutex|throttle|rate.limit|max_conn)/im.test(diff)
4106
+ },
4107
+ {
4108
+ name: "async_pattern_fix",
4109
+ test: (diff) => /^\+.*(@MainActor|async with|asyncio\.timeout|\.await)/.test(diff) && /^-/m.test(diff)
4110
+ },
4111
+ {
4112
+ name: "added_missing_import",
4113
+ test: (diff) => /^\+.*(import|from.*import|require)/.test(diff) && !/^-.*(import|from.*import|require)/m.test(diff)
4114
+ }
4115
+ ];
4116
+ function getSessionFlagPath(sessionId) {
4117
+ const dir = join6(tmpdir2(), "massu-auto-learning");
4118
+ if (!existsSync9(dir)) {
4119
+ mkdirSync7(dir, { recursive: true });
4120
+ }
4121
+ return join6(dir, `fixes-${sessionId.slice(0, 12)}.jsonl`);
4122
+ }
4123
+ function _stateDir() {
4124
+ const dir = join6(tmpdir2(), "massu-fix-detector-state");
4125
+ if (!existsSync9(dir)) {
4126
+ mkdirSync7(dir, { recursive: true });
4127
+ }
4128
+ return dir;
4129
+ }
4130
+ function _cwdHash(cwd) {
4131
+ let h = 5381;
4132
+ for (let i = 0; i < cwd.length; i++) {
4133
+ h = (h << 5) + h + cwd.charCodeAt(i) | 0;
4134
+ }
4135
+ return Math.abs(h).toString(36);
4136
+ }
4137
+ function isGitWorkTreeFast(cwd) {
4138
+ try {
4139
+ const cachePath = join6(_stateDir(), `worktree-${_cwdHash(cwd)}.json`);
4140
+ if (existsSync9(cachePath)) {
4141
+ const cached = JSON.parse(readFileSync6(cachePath, "utf-8"));
4142
+ const ageMs = Date.now() - cached.ts;
4143
+ if (ageMs < 36e5) return cached.isWorkTree;
4144
+ }
4145
+ let isWorkTree = false;
4146
+ try {
4147
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, timeout: 500, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
4148
+ isWorkTree = true;
4149
+ } catch {
4150
+ isWorkTree = false;
4151
+ }
4152
+ try {
4153
+ appendFileSync3(cachePath, "");
4154
+ const fs = __require("fs");
4155
+ fs.writeFileSync(cachePath, JSON.stringify({ isWorkTree, ts: Date.now() }));
4156
+ } catch {
4157
+ }
4158
+ return isWorkTree;
4159
+ } catch {
4160
+ return false;
4161
+ }
4162
+ }
4163
+ function _disabledFlagPath(sessionId) {
4164
+ return join6(_stateDir(), `disabled-${sessionId.slice(0, 12)}.flag`);
4165
+ }
4166
+ function isSessionAutoDisabled(sessionId) {
4167
+ try {
4168
+ return existsSync9(_disabledFlagPath(sessionId));
4169
+ } catch {
4170
+ return false;
4171
+ }
4172
+ }
4173
+ function markSessionAutoDisabled(sessionId, elapsedMs) {
4174
+ try {
4175
+ const fs = __require("fs");
4176
+ fs.writeFileSync(_disabledFlagPath(sessionId), `disabled: previous git diff took ${elapsedMs}ms
4177
+ `);
4178
+ } catch {
4179
+ }
4180
+ }
4181
+ async function main() {
4182
+ try {
4183
+ const input = await readStdin();
4184
+ const hookInput = JSON.parse(input);
4185
+ const filePath = hookInput.tool_input?.file_path;
4186
+ if (!filePath || !existsSync9(filePath)) {
4187
+ process.exit(0);
4188
+ return;
4189
+ }
4190
+ if (!/\.(py|swift|ts|tsx|js|jsx|rs|go|rb|sh)$/.test(filePath)) {
4191
+ process.exit(0);
4192
+ return;
4193
+ }
4194
+ const config = getConfig();
4195
+ const incidentDir = config.autoLearning?.incidentDir ?? "docs/incidents";
4196
+ const memoryDir = config.autoLearning?.memoryDir ?? "memory";
4197
+ if (filePath.includes(incidentDir) || filePath.includes(memoryDir) || filePath.includes("MEMORY.md")) {
4198
+ process.exit(0);
4199
+ return;
4200
+ }
4201
+ if (isTestOrFixturePath(filePath)) {
4202
+ process.exit(0);
4203
+ return;
4204
+ }
4205
+ if (config.autoLearning?.enabled === false || config.autoLearning?.fixDetection?.enabled === false) {
4206
+ process.exit(0);
4207
+ return;
4208
+ }
4209
+ const root = getProjectRoot();
4210
+ if (!isGitWorkTreeFast(root) || isSessionAutoDisabled(hookInput.session_id)) {
4211
+ process.exit(0);
4212
+ return;
4213
+ }
4214
+ let diff = "";
4215
+ try {
4216
+ const startMs = Date.now();
4217
+ diff = execFileSync("git", ["diff", "--", filePath], { cwd: root, timeout: 3e3, encoding: "utf-8" });
4218
+ if (!diff) {
4219
+ diff = execFileSync("git", ["diff", "HEAD", "--", filePath], { cwd: root, timeout: 3e3, encoding: "utf-8" });
4220
+ }
4221
+ const elapsedMs = Date.now() - startMs;
4222
+ if (elapsedMs > 2e3) {
4223
+ markSessionAutoDisabled(hookInput.session_id, elapsedMs);
4224
+ }
4225
+ } catch {
4226
+ process.exit(0);
4227
+ return;
4228
+ }
4229
+ if (!diff) {
4230
+ process.exit(0);
4231
+ return;
4232
+ }
4233
+ const enabledSignals = new Set(config.autoLearning?.fixDetection?.signals ?? FIX_HEURISTICS.map((h) => h.name));
4234
+ const detected = [];
4235
+ for (const heuristic of FIX_HEURISTICS) {
4236
+ if (enabledSignals.has(heuristic.name) && heuristic.test(diff)) {
4237
+ detected.push(heuristic.name);
4238
+ }
4239
+ }
4240
+ if (detected.length === 0) {
4241
+ process.exit(0);
4242
+ return;
4243
+ }
4244
+ const signal = {
4245
+ file: filePath,
4246
+ signals: detected,
4247
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4248
+ };
4249
+ const flagPath = getSessionFlagPath(hookInput.session_id);
4250
+ appendFileSync3(flagPath, JSON.stringify(signal) + "\n");
4251
+ const lines = readFileSync6(flagPath, "utf-8").split("\n").filter(Boolean);
4252
+ if (lines.length === 1) {
4253
+ writeHookContext(
4254
+ HOOK_EVENT,
4255
+ `[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.`
4256
+ );
4257
+ }
4258
+ } catch (err) {
4259
+ recordHookFailure("fix-detector", err);
4260
+ }
4261
+ process.exit(0);
4262
+ }
4263
+ function readStdin() {
4264
+ return new Promise((resolve5) => {
4265
+ let data = "";
4266
+ process.stdin.setEncoding("utf-8");
4267
+ process.stdin.on("data", (chunk) => {
4268
+ data += chunk;
4269
+ });
4270
+ process.stdin.on("end", () => resolve5(data));
4271
+ setTimeout(() => resolve5(data), 3e3);
4272
+ });
4273
+ }
4274
+ if (isDirectInvocation(import.meta.url)) {
4275
+ main();
3985
4276
  }
3986
4277
 
3987
4278
  // src/hooks/classify-failure.ts
3988
4279
  init_hook_failure_signal();
4280
+ var HOOK_EVENT2 = "PostToolUse";
3989
4281
  var BUG_FIX_INDICATORS = [
3990
4282
  /\b(catch|error|throw|fail|fix|bug|broken|missing|crash|wrong|typo|incorrect)\b/i
3991
4283
  ];
@@ -3993,7 +4285,7 @@ var CODE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|swift|rs|go|rb|sh)$/;
3993
4285
  function getDedupeMarkerPath(sessionId, filePath) {
3994
4286
  const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3995
4287
  const hash = simpleHash(filePath);
3996
- return join6(tmpdir2(), `massu-classify-${day}-${sessionId.slice(0, 8)}-${hash}`);
4288
+ return join7(tmpdir3(), `massu-classify-${day}-${sessionId.slice(0, 8)}-${hash}`);
3997
4289
  }
3998
4290
  function simpleHash(str) {
3999
4291
  let h = 0;
@@ -4003,13 +4295,13 @@ function simpleHash(str) {
4003
4295
  return Math.abs(h).toString(36);
4004
4296
  }
4005
4297
  function readFailureContextFiles() {
4006
- const dir = tmpdir2();
4298
+ const dir = tmpdir3();
4007
4299
  let context = "";
4008
4300
  try {
4009
4301
  const files = readdirSync2(dir).filter((f) => f.startsWith("massu-failure-context-"));
4010
4302
  for (const file of files) {
4011
4303
  try {
4012
- context += " " + readFileSync6(join6(dir, file), "utf-8");
4304
+ context += " " + readFileSync7(join7(dir, file), "utf-8");
4013
4305
  } catch {
4014
4306
  }
4015
4307
  }
@@ -4018,21 +4310,21 @@ function readFailureContextFiles() {
4018
4310
  return context.trim();
4019
4311
  }
4020
4312
  function cleanupFailureContextFiles() {
4021
- const dir = tmpdir2();
4313
+ const dir = tmpdir3();
4022
4314
  try {
4023
4315
  const files = readdirSync2(dir).filter((f) => f.startsWith("massu-failure-context-"));
4024
4316
  for (const file of files) {
4025
4317
  try {
4026
- unlinkSync2(join6(dir, file));
4318
+ unlinkSync2(join7(dir, file));
4027
4319
  } catch {
4028
4320
  }
4029
4321
  }
4030
4322
  } catch {
4031
4323
  }
4032
4324
  }
4033
- async function main() {
4325
+ async function main2() {
4034
4326
  try {
4035
- const input = await readStdin();
4327
+ const input = await readStdin2();
4036
4328
  const hookInput = JSON.parse(input);
4037
4329
  const filePath = hookInput.tool_input?.file_path;
4038
4330
  if (!filePath) {
@@ -4056,6 +4348,10 @@ async function main() {
4056
4348
  process.exit(0);
4057
4349
  return;
4058
4350
  }
4351
+ if (isTestOrFixturePath(relPath) || isTestOrFixturePath(filePath)) {
4352
+ process.exit(0);
4353
+ return;
4354
+ }
4059
4355
  const oldString = hookInput.tool_input?.old_string ?? "";
4060
4356
  const newString = hookInput.tool_input?.new_string ?? "";
4061
4357
  const content = hookInput.tool_input?.content ?? "";
@@ -4085,7 +4381,7 @@ async function main() {
4085
4381
  return;
4086
4382
  }
4087
4383
  const dedupeMarker = getDedupeMarkerPath(hookInput.session_id, filePath);
4088
- if (existsSync9(dedupeMarker)) {
4384
+ if (existsSync10(dedupeMarker)) {
4089
4385
  process.exit(0);
4090
4386
  return;
4091
4387
  }
@@ -4127,7 +4423,7 @@ function outputKnownPattern(match) {
4127
4423
  lines.push(` Covered by: ${match.rules.join(", ")}`);
4128
4424
  }
4129
4425
  lines.push(" No new rules needed. Reference existing incident if logging.");
4130
- writeHookMessage(lines.join("\n"));
4426
+ writeHookContext(HOOK_EVENT2, lines.join("\n"));
4131
4427
  }
4132
4428
  function outputSimilarPattern(match) {
4133
4429
  const lines = [];
@@ -4137,7 +4433,7 @@ function outputSimilarPattern(match) {
4137
4433
  lines.push(` Check if existing rules cover this case: ${match.rules.join(", ")}`);
4138
4434
  }
4139
4435
  lines.push(" If genuinely new: create incident + prevention rule + enforcement.");
4140
- writeHookMessage(lines.join("\n"));
4436
+ writeHookContext(HOOK_EVENT2, lines.join("\n"));
4141
4437
  }
4142
4438
  function outputNewPattern(fileName, match) {
4143
4439
  const lines = [];
@@ -4151,9 +4447,9 @@ function outputNewPattern(fileName, match) {
4151
4447
  lines.push(" 1. INCIDENT REPORT");
4152
4448
  lines.push(" 2. PREVENTION RULE (if new failure pattern)");
4153
4449
  lines.push(" 3. ENFORCEMENT (hook or static check)");
4154
- writeHookMessage(lines.join("\n"));
4450
+ writeHookContext(HOOK_EVENT2, lines.join("\n"));
4155
4451
  }
4156
- function readStdin() {
4452
+ function readStdin2() {
4157
4453
  return new Promise((resolve5) => {
4158
4454
  let data = "";
4159
4455
  process.stdin.setEncoding("utf-8");
@@ -4164,4 +4460,6 @@ function readStdin() {
4164
4460
  setTimeout(() => resolve5(data), 3e3);
4165
4461
  });
4166
4462
  }
4167
- main();
4463
+ if (isDirectInvocation(import.meta.url)) {
4464
+ main2();
4465
+ }
@@ -1167,6 +1167,13 @@ var init_config = __esm({
1167
1167
  });
1168
1168
  LanguageFrameworkEntrySchema = z2.object({
1169
1169
  framework: z2.string().optional(),
1170
+ // `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
1171
+ // by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
1172
+ // `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
1173
+ // derives the index builders' candidate set from it, so it is typed here rather than
1174
+ // read as an unknown: a consumer that has to shape-check its own SoT is one branch away
1175
+ // from silently contributing `undefined` to a path predicate.
1176
+ source_dirs: z2.array(z2.string()).optional(),
1170
1177
  test_framework: z2.string().optional(),
1171
1178
  test: z2.string().optional(),
1172
1179
  runtime: z2.string().optional(),
@@ -1339,6 +1346,16 @@ var init_memory_vector = __esm({
1339
1346
  }
1340
1347
  });
1341
1348
 
1349
+ // src/lib/timestamps.ts
1350
+ function nowIso() {
1351
+ return (/* @__PURE__ */ new Date()).toISOString();
1352
+ }
1353
+ var init_timestamps = __esm({
1354
+ "src/lib/timestamps.ts"() {
1355
+ "use strict";
1356
+ }
1357
+ });
1358
+
1342
1359
  // src/db-backup.ts
1343
1360
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1344
1361
  import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
@@ -3670,8 +3687,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3670
3687
  }
3671
3688
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3672
3689
  const result = db.prepare(`
3673
- INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
3674
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3690
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3691
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3675
3692
  `).run(
3676
3693
  sessionId,
3677
3694
  turnNumber,
@@ -3680,14 +3697,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3680
3697
  toolCallsJson,
3681
3698
  toolCallCount,
3682
3699
  promptTokens,
3683
- responseTokens
3700
+ responseTokens,
3701
+ nowIso()
3684
3702
  );
3685
3703
  return Number(result.lastInsertRowid);
3686
3704
  }
3687
3705
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3688
3706
  db.prepare(`
3689
- INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
3690
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3707
+ INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved, created_at)
3708
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3691
3709
  `).run(
3692
3710
  sessionId,
3693
3711
  turnNumber,
@@ -3696,7 +3714,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3696
3714
  inputSize,
3697
3715
  outputSize,
3698
3716
  success ? 1 : 0,
3699
- filesInvolved ? JSON.stringify(filesInvolved) : null
3717
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3718
+ nowIso()
3700
3719
  );
3701
3720
  }
3702
3721
  function getLastProcessedLine(db, sessionId) {
@@ -3955,6 +3974,7 @@ var init_memory_db = __esm({
3955
3974
  init_db_driver();
3956
3975
  init_config();
3957
3976
  init_memory_vector();
3977
+ init_timestamps();
3958
3978
  init_db_backup();
3959
3979
  init_rule_delivery();
3960
3980
  init_rule_candidate_store();
@@ -4072,6 +4092,21 @@ function toolResponseText(raw) {
4072
4092
 
4073
4093
  // src/hooks/cost-tracker.ts
4074
4094
  init_hook_failure_signal();
4095
+ init_timestamps();
4096
+
4097
+ // src/hooks/lib/is-direct-invocation.ts
4098
+ import { pathToFileURL } from "url";
4099
+ function isDirectInvocation(moduleUrl) {
4100
+ const entry = process.argv[1];
4101
+ if (!entry) return false;
4102
+ try {
4103
+ return moduleUrl === pathToFileURL(entry).href;
4104
+ } catch {
4105
+ return false;
4106
+ }
4107
+ }
4108
+
4109
+ // src/hooks/cost-tracker.ts
4075
4110
  var CHARS_PER_TOKEN = 4;
4076
4111
  function estimateTokens(text) {
4077
4112
  return Math.ceil(text.length / CHARS_PER_TOKEN);
@@ -4087,9 +4122,9 @@ async function main() {
4087
4122
  const db = getMemoryDb();
4088
4123
  try {
4089
4124
  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, "");
4125
+ INSERT INTO tool_cost_events (session_id, tool_name, estimated_input_tokens, estimated_output_tokens, model, created_at)
4126
+ VALUES (?, ?, ?, ?, ?, ?)
4127
+ `).run(session_id, tool_name, estimatedInputTokens, estimatedOutputTokens, "", nowIso());
4093
4128
  } finally {
4094
4129
  db.close();
4095
4130
  }
@@ -4109,4 +4144,6 @@ function readStdin() {
4109
4144
  setTimeout(() => resolve5(data), 3e3);
4110
4145
  });
4111
4146
  }
4112
- main();
4147
+ if (isDirectInvocation(import.meta.url)) {
4148
+ main();
4149
+ }