chat-recall 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/watch.js CHANGED
@@ -6141,6 +6141,24 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
6141
6141
  }
6142
6142
  const filesToParse = hasSubagentsDir(sessionPath) ? [sessionPath, ...getSubagentPaths(sessionPath)] : [sessionPath];
6143
6143
  let userCount = 0;
6144
+ const seenPrompts = /* @__PURE__ */ new Set();
6145
+ const recordPrompt = (raw, line, dedupe = false) => {
6146
+ if (userCount >= maxMessages)
6147
+ return;
6148
+ const withoutReminders = raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
6149
+ if (!withoutReminders.trim() || withoutReminders.trim().length <= 10)
6150
+ return;
6151
+ const cleanedText = stripInjectedBanners(withoutReminders).trim();
6152
+ if (cleanedText.length < 10)
6153
+ return;
6154
+ if (dedupe && seenPrompts.has(cleanedText))
6155
+ return;
6156
+ seenPrompts.add(cleanedText);
6157
+ content.userMessages.push({ text: cleanedText, lineNumber: line, contentType: "user" });
6158
+ userCount++;
6159
+ if (!content.firstPrompt)
6160
+ content.firstPrompt = cleanedText.slice(0, 1e3);
6161
+ };
6144
6162
  let assistantCount = 0;
6145
6163
  let toolCount = 0;
6146
6164
  let lineNum = 0;
@@ -6219,19 +6237,11 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
6219
6237
  text = textParts.join("\n");
6220
6238
  }
6221
6239
  }
6222
- if (text && !text.includes("<system-reminder>") && text.trim().length > 10) {
6223
- const cleanedText = stripInjectedBanners(text).trim();
6224
- if (cleanedText.length < 10)
6225
- continue;
6226
- content.userMessages.push({
6227
- text: cleanedText,
6228
- lineNumber: lineNum,
6229
- contentType: "user"
6230
- });
6231
- userCount++;
6232
- if (!content.firstPrompt) {
6233
- content.firstPrompt = cleanedText.slice(0, 1e3);
6234
- }
6240
+ if (text)
6241
+ recordPrompt(text, lineNum);
6242
+ } else if (msgType === "queue-operation") {
6243
+ if (obj.operation === "enqueue" && typeof obj.content === "string") {
6244
+ recordPrompt(obj.content, lineNum, true);
6235
6245
  }
6236
6246
  } else if (msgType === "assistant") {
6237
6247
  const msg = obj.message;
@@ -8566,6 +8576,9 @@ var init_generic_engine = __esm({
8566
8576
  // ../engine/dist/core/backends/claude.js
8567
8577
  import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
8568
8578
  import { join as join10, basename as basename8 } from "path";
8579
+ function stripReminders(text) {
8580
+ return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
8581
+ }
8569
8582
  function parseExitCode(text) {
8570
8583
  const m = text.match(/(?:^|\n)\s*(?:exit\s+code|returncode|exit\s+status|exit)\s*[:=]\s*(\d+)\b/i);
8571
8584
  return m ? Number(m[1]) : void 0;
@@ -8874,6 +8887,15 @@ var init_claude2 = __esm({
8874
8887
  }
8875
8888
  const tsIso = typeof obj.timestamp === "string" ? obj.timestamp : void 0;
8876
8889
  const ts2 = tsIso ? Date.parse(tsIso) || mtime : mtime;
8890
+ if (obj.type === "queue-operation") {
8891
+ if (obj.operation === "enqueue" && typeof obj.content === "string") {
8892
+ const queued = stripReminders(obj.content);
8893
+ if (queued && !queued.startsWith("<task-notification")) {
8894
+ events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text: queued });
8895
+ }
8896
+ }
8897
+ continue;
8898
+ }
8877
8899
  if (obj.type === "user") {
8878
8900
  const msg = obj.message;
8879
8901
  const content = msg?.content;
@@ -8907,9 +8929,9 @@ var init_claude2 = __esm({
8907
8929
  }
8908
8930
  }
8909
8931
  }
8910
- if (text && !text.includes("<system-reminder>")) {
8911
- events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text });
8912
- }
8932
+ const spoken = stripReminders(text);
8933
+ if (spoken)
8934
+ events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text: spoken });
8913
8935
  for (const tr of toolResults) {
8914
8936
  events.push({
8915
8937
  kind: "tool_result",
@@ -10524,23 +10546,24 @@ var init_codex2 = __esm({
10524
10546
  return null;
10525
10547
  }
10526
10548
  /**
10527
- * Stream a Codex JSONL rollout into canonical events. Codex separates:
10528
- * - event_msg + payload.type='user_message' → user
10529
- * - response_item + payload.type='message' → assistant_text
10530
- * - response_item + payload.type='function_call' → tool_use
10531
- * - response_item + payload.type='function_call_output' → tool_result
10532
- *
10533
- * Filters Codex's wrapper-bearing user messages (environment_context etc.).
10534
- *
10535
- * **Subagent fan-out:** Codex orchestrates work via `spawn_agent` calls
10536
- * that produce sibling rollout files in the same YYYY/MM/DD directory.
10537
- * Sub-agent rollouts carry `payload.source.subagent.thread_spawn` linking
10538
- * back to the parent. The actual file edits often happen inside those
10539
- * subagent rollouts — without folding their events into the parent's
10540
- * stream, replay/scan would see the parent as edit-free. We append every
10541
- * subagent rollout's events here so the generic engine sees one unified
10542
- * stream per user-facing session.
10543
- */
10549
+ * Stream a Codex JSONL rollout into canonical events. Codex separates:
10550
+ * - event_msg + payload.type='user_message' → user
10551
+ * - response_item + payload.type='message' → user OR assistant_text,
10552
+ * by payload.role
10553
+ * - response_item + payload.type='function_call' → tool_use
10554
+ * - response_item + payload.type='function_call_output' → tool_result
10555
+ *
10556
+ * Filters Codex's wrapper-bearing user messages (environment_context etc.).
10557
+ *
10558
+ * **Subagent fan-out:** Codex orchestrates work via `spawn_agent` calls
10559
+ * that produce sibling rollout files in the same YYYY/MM/DD directory.
10560
+ * Sub-agent rollouts carry `payload.source.subagent.thread_spawn` linking
10561
+ * back to the parent. The actual file edits often happen inside those
10562
+ * subagent rollouts — without folding their events into the parent's
10563
+ * stream, replay/scan would see the parent as edit-free. We append every
10564
+ * subagent rollout's events here so the generic engine sees one unified
10565
+ * stream per user-facing session.
10566
+ */
10544
10567
  readEvents(rawId) {
10545
10568
  const located = findCodexSessionFile(this.toRawId(rawId));
10546
10569
  if (!located)
@@ -10610,6 +10633,11 @@ var init_codex2 = __esm({
10610
10633
  if (text && !isCodexInjectedWrapper2(text)) {
10611
10634
  events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text });
10612
10635
  }
10636
+ } else if (obj.type === "response_item" && payload.type === "message" && payload.role === "user") {
10637
+ const text = flattenContent(payload.content).trim();
10638
+ if (text && !isCodexInjectedWrapper2(text)) {
10639
+ events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text });
10640
+ }
10613
10641
  } else if (obj.type === "response_item" && payload.type === "message" && payload.role === "assistant") {
10614
10642
  const text = flattenContent(payload.content).trim();
10615
10643
  if (text)
@@ -17111,7 +17139,9 @@ var init_extractor_version = __esm({
17111
17139
  "use strict";
17112
17140
  BASE_EXTRACTOR_VERSION = 2;
17113
17141
  TOOL_EXTRACTOR_BUMP = {
17114
- agy: 2
17142
+ agy: 2,
17143
+ claude: 1,
17144
+ codex: 1
17115
17145
  };
17116
17146
  EXTRACTOR_VERSION = BASE_EXTRACTOR_VERSION;
17117
17147
  }
@@ -17429,7 +17459,7 @@ var init_http = __esm({
17429
17459
  CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
17430
17460
  } catch {
17431
17461
  try {
17432
- if (true) CLI_VERSION = "0.5.3";
17462
+ if (true) CLI_VERSION = "0.5.5";
17433
17463
  } catch {
17434
17464
  }
17435
17465
  }
@@ -17996,6 +18026,17 @@ async function syncToTarget(cred, opts = {}) {
17996
18026
  if (res.status === 401 || res.status === 403) {
17997
18027
  throw new Error(`server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base2}`);
17998
18028
  }
18029
+ if (res.status === 402) {
18030
+ let detail = text;
18031
+ try {
18032
+ const body2 = JSON.parse(text);
18033
+ detail = body2.error ?? text;
18034
+ if (body2.resetsAt) detail += ` (resets ${new Date(body2.resetsAt).toISOString().slice(0, 10)})`;
18035
+ if (body2.upgradeUrl) detail += ` \u2014 ${body2.upgradeUrl}`;
18036
+ } catch {
18037
+ }
18038
+ throw new Error(`sync paused by the server: ${detail}`);
18039
+ }
17999
18040
  if (!retryable) throw new Error(`sync failed: HTTP ${res.status} ${text}`);
18000
18041
  if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
18001
18042
  const retryAfterS = Number(res.headers.get("retry-after"));
@@ -18880,7 +18921,7 @@ var init_sync_client = __esm({
18880
18921
  ownVersion = JSON.parse(readFileSync33(join40(dir, "../package.json"), "utf-8")).version || ownVersion;
18881
18922
  } catch {
18882
18923
  try {
18883
- if (true) ownVersion = "0.5.3";
18924
+ if (true) ownVersion = "0.5.5";
18884
18925
  } catch {
18885
18926
  }
18886
18927
  }
@@ -19863,6 +19904,14 @@ async function collectCode(opts) {
19863
19904
  agentPrompt: manifestPrompt(v.violation_type ?? "", file, v.line ?? null, v.description ?? "")
19864
19905
  });
19865
19906
  }
19907
+ {
19908
+ const before = findings.length;
19909
+ const own = findings.filter((f) => !f.file || !isGenerated(f.file));
19910
+ if (own.length !== before) {
19911
+ findings.splice(0, before, ...own);
19912
+ log6(`dropped ${before - own.length} finding(s) in vendored/generated files`);
19913
+ }
19914
+ }
19866
19915
  const godSet = new Set((coup?.god_modules ?? []).map((m) => rel(m.file)));
19867
19916
  const cloneSet = /* @__PURE__ */ new Set();
19868
19917
  for (const g of clones?.groups ?? [])
@@ -19948,7 +19997,9 @@ async function collectCode(opts) {
19948
19997
  }
19949
19998
  for (const g of clones?.groups ?? []) {
19950
19999
  if ((g.count ?? 0) >= 3 || (g.lines ?? 0) >= 12) {
19951
- const fns = (g.functions ?? []).filter((x) => x && typeof x === "object");
20000
+ const fns = (g.functions ?? []).filter((x) => x && typeof x === "object").filter((x) => !isGenerated(rel(x.file)));
20001
+ if (fns.length < 2)
20002
+ continue;
19952
20003
  const names = [...new Set(fns.map((x) => x.name))];
19953
20004
  const locs = fns.map((x) => rel(x.file)).slice(0, 5);
19954
20005
  pushAction((g.lines ?? 0) * (g.count ?? 0) >= 150 ? 1 : 2, "duplication", `${names.join(", ")} copy-pasted ${g.count}\xD7 (${g.lines} lines each)`, "Extract one shared implementation and replace the copies.", locs.map((f) => ({ file: f })), clonePrompt(names, g.lines ?? 0, g.count ?? 0, locs));
@@ -19967,10 +20018,14 @@ async function collectCode(opts) {
19967
20018
  }
19968
20019
  for (const m of (coup?.god_modules ?? []).slice(0, 5)) {
19969
20020
  const file = rel(m.file);
20021
+ if (isGenerated(file))
20022
+ continue;
19970
20023
  pushAction(2, "modularity", `God module ${file} (fan-in ${m.fan_in}, fan-out ${m.fan_out})`, "Split into cohesive sub-modules; introduce interfaces to cut fan-out.", [{ file }], couplingPrompt(file, m.fan_in ?? 0, m.fan_out ?? 0, Math.round((m.instability ?? 0) * 100) / 100));
19971
20024
  }
19972
20025
  for (const c of (cyc?.cycles ?? []).slice(0, 5)) {
19973
- const chain = (c.files ?? []).map(rel);
20026
+ const chain = (c.files ?? []).map(rel).filter((f) => !isGenerated(f));
20027
+ if (chain.length < 2)
20028
+ continue;
19974
20029
  pushAction(2, "architecture", `Circular dependency (${chain.length} files)`, "Break the cycle: extract the shared type/interface into a separate module.", chain.slice(0, 4).map((f) => ({ file: f })), cyclePrompt(chain.slice(0, 6)));
19975
20030
  }
19976
20031
  for (const h of hotspotsAll.slice(0, 5)) {
@@ -20109,7 +20164,31 @@ var init_collector = __esm({
20109
20164
  "migration_parity",
20110
20165
  "manifest_compliance"
20111
20166
  ];
20112
- GENERATED = ["generated/", ".pb.go", ".gen.", "_pb2.py", "/gen/", ".g.dart", "/node_modules/", "/vendor/", ".lock"];
20167
+ GENERATED = [
20168
+ "generated/",
20169
+ "__generated__/",
20170
+ ".pb.go",
20171
+ ".gen.",
20172
+ "_pb2.py",
20173
+ "/gen/",
20174
+ ".g.dart",
20175
+ "/node_modules/",
20176
+ "/vendor/",
20177
+ "/third_party/",
20178
+ "/site-packages/",
20179
+ ".lock",
20180
+ "/dist/",
20181
+ "/build/",
20182
+ "/out/",
20183
+ "/target/",
20184
+ "/.next/",
20185
+ "/coverage/",
20186
+ ".min.js",
20187
+ ".min.css",
20188
+ ".bundle.js",
20189
+ "-bundle.js",
20190
+ ".map"
20191
+ ];
20113
20192
  CF_RE = /\b(if|for|while|switch|case|catch|elif|when|loop|&&|\|\||\?)\b|\?/g;
20114
20193
  DEPTH = 2;
20115
20194
  MCP_OUT_MAX_BYTES = 64 * 1024 * 1024;
@@ -20959,7 +21038,7 @@ try {
20959
21038
  ownVersion2 = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion2;
20960
21039
  } catch {
20961
21040
  try {
20962
- if (true) ownVersion2 = "0.5.3";
21041
+ if (true) ownVersion2 = "0.5.5";
20963
21042
  } catch {
20964
21043
  }
20965
21044
  }
@@ -21134,7 +21213,7 @@ async function runCollectorMigration(opts) {
21134
21213
 
21135
21214
  // auto-indexer/indexer.ts
21136
21215
  import { existsSync as existsSync44, readFileSync as readFileSync40 } from "fs";
21137
- var CLI_VERSION3 = true ? "0.5.3" : "0.0.0";
21216
+ var CLI_VERSION3 = true ? "0.5.5" : "0.0.0";
21138
21217
  var DEBOUNCE_MS = 5e3;
21139
21218
  function ts() {
21140
21219
  return (/* @__PURE__ */ new Date()).toISOString();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-recall",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/munhq/chat-recall.git"
@@ -31,6 +31,7 @@ read except these, which change stored memory or state — treat each as an edit
31
31
  and apply the test in `chat-recall-memory` before calling one:
32
32
  `recall_kg_add`, `recall_kg_invalidate`, `recall_decision_record`,
33
33
  `recall_diary_write`, `recall_set`, `recall_task_create`, `recall_task_update`,
34
+ `recall_task_comment`,
34
35
  `recall_security_dismiss`, `recall_rename_session`, `recall_regenerate_summary`,
35
36
  `recall_reclassify`, `recall_index`, `recall_code_index`.
36
37
 
@@ -65,9 +66,9 @@ tool is registered and never named here, so adding a tool means placing it.
65
66
  **Findings, ranked** — `recall_claude_suggestions` (every finding that becomes an agent-instruction change — the CLAUDE.md rules and skill installs — merged across account scope and every indexed project) and `recall_improvements` (everything else, most urgent first; `create_tasks: true` opens one team task per item). They split the same engines, so an item never appears in both. Neither needs the codeindex binary: without it you still get the account-level half.
66
67
  **What changed** — `recall_edits_timeline` (cross-tool edits), `recall_diff` (per-session diffs), `recall_commits` (did it actually land), `recall_markers` (where a session went sideways).
67
68
  **Durable memory / KG** — `recall_kg_query` / `recall_kg_add` / `recall_kg_invalidate` / `recall_kg_timeline` / `recall_kg_stats`, `recall_decision_record`, `recall_diary_write` / `recall_diary_read`, `recall_set` / `recall_get`.
68
- **Team** — `recall_tasks` (the shared task board), `recall_task_create`, `recall_task_update` (status, assignee, comment; task ids look like `t_…`), `recall_team_activity` (per-teammate × per-project rollup), `recall_shares` (which projects are shared — private by default, nothing is visible to teammates until shared).
69
+ **Team** — `recall_tasks` (the shared task board), `recall_task_create`, `recall_task_update` (status, assignee, linked session; task ids look like `t_…`; when you start work on a task, set it `in_progress` and pass your session id as `linked_session_id` so the board can verify the work shipped), `recall_task_comment` (leave a progress note on a card), `recall_team_activity` (per-teammate × per-project rollup), `recall_shares` (which projects are shared — private by default, nothing is visible to teammates until shared).
69
70
  **Security** — `recall_security_summary` (leaked secrets that still need action — start here for "did I paste a key somewhere"), `recall_security_session` (findings for one session), `recall_security_dismiss` (mark rotated / false positive — do this only when the user confirms which), `recall_security_rules` (tenant detection rules; also tests a regex).
70
- **Health / maintenance** — `recall_status` (is the index alive, what is synced), `recall_index` (sync now), `recall_heal_audit` (sessions whose rendered text is thinner than the raw archive, i.e. truncated upstream), `recall_regenerate_summary` (stored summary is stale or wrong), `recall_reclassify` (re-run the classifier over old chunks), `recall_rename_session`.
71
+ **Health / maintenance** — `recall_status` (is the index alive, what is synced), `recall_index` (sync now), `recall_help` (names the tools the lean profile leaves unlisted — they all still work by name), `recall_heal_audit` (sessions whose rendered text is thinner than the raw archive, i.e. truncated upstream), `recall_regenerate_summary` (stored summary is stale or wrong), `recall_reclassify` (re-run the classifier over old chunks), `recall_rename_session`.
71
72
 
72
73
  ## If you're unsure where to start
73
74