chat-recall 0.6.4 → 0.6.6

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/mcp-relay.js CHANGED
@@ -118,7 +118,7 @@ function daemonEnabled() {
118
118
  }
119
119
 
120
120
  // src/mcp-relay.ts
121
- var VERSION = true ? "0.6.4" : "0.0.0";
121
+ var VERSION = true ? "0.6.6" : "0.0.0";
122
122
  var SERVER_URL = new URL("./mcp.js", import.meta.url);
123
123
  var SERVER_PATH = fileURLToPath(SERVER_URL);
124
124
  var START_TIMEOUT_MS = 15e3;
package/dist/mcp.js CHANGED
@@ -15259,12 +15259,12 @@ function extractEntities(text, context = {}) {
15259
15259
  if (chosen && looksLikeStrictTool(chosen) && looksLikeStrictTool(rejected)) {
15260
15260
  const c = canonTool(chosen), r = canonTool(rejected);
15261
15261
  if (projectName)
15262
- addTriple(projectName, "chose", c, 0.8);
15263
- addTriple(projectName || "project", "rejected", r, 0.7);
15264
- addTriple(c, "chosen_over", r, 0.8);
15262
+ addTriple(projectName, "chose", c, DECISION_GUESS_CONFIDENCE);
15263
+ addTriple(projectName || "project", "rejected", r, DECISION_GUESS_CONFIDENCE);
15264
+ addTriple(c, "chosen_over", r, DECISION_GUESS_CONFIDENCE);
15265
15265
  }
15266
15266
  } else if (chosen && looksLikeDecisionObject(chosen) && projectName) {
15267
- addTriple(projectName, "chose", canonTool(chosen), 0.8);
15267
+ addTriple(projectName, "chose", canonTool(chosen), DECISION_GUESS_CONFIDENCE);
15268
15268
  }
15269
15269
  }
15270
15270
  }
@@ -15301,7 +15301,7 @@ function extractEntities(text, context = {}) {
15301
15301
  }
15302
15302
  return triples;
15303
15303
  }
15304
- var KNOWN_TOOLS, AMBIGUOUS_TOOLS, toolContextRe, TOOL_PATTERNS, DECISION_PATTERNS2, DECISION_OBJECT_STOPWORDS, KNOWN_TOOL_NAMES, CANONICAL_TOOL, canonTool, PROJECT_PATH_PATTERN, PEOPLE_PATTERNS, NOT_PEOPLE;
15304
+ var KNOWN_TOOLS, AMBIGUOUS_TOOLS, toolContextRe, TOOL_PATTERNS, DECISION_PATTERNS2, DECISION_OBJECT_STOPWORDS, KNOWN_TOOL_NAMES, CANONICAL_TOOL, canonTool, DECISION_GUESS_CONFIDENCE, PROJECT_PATH_PATTERN, PEOPLE_PATTERNS, NOT_PEOPLE;
15305
15305
  var init_entity_extractor = __esm({
15306
15306
  "../engine/dist/core/entity-extractor.js"() {
15307
15307
  "use strict";
@@ -15522,6 +15522,7 @@ var init_entity_extractor = __esm({
15522
15522
  "nextjs": "next.js"
15523
15523
  };
15524
15524
  canonTool = (n) => CANONICAL_TOOL[n.toLowerCase()] ?? n;
15525
+ DECISION_GUESS_CONFIDENCE = 0.6;
15525
15526
  PROJECT_PATH_PATTERN = /\/(?:home|Users)\/\w+\/(?:code|projects|work|dev|src)\/(?:\w+\/)?([a-zA-Z][a-zA-Z0-9_-]{1,40})/g;
15526
15527
  PEOPLE_PATTERNS = [
15527
15528
  /\b(?:my name is|I'm|I am)\s+([A-Z][a-z]{1,15}(?:\s+[A-Z][a-z]{1,15})?)/g,
@@ -19727,7 +19728,7 @@ var init_http = __esm({
19727
19728
  CLI_VERSION = JSON.parse(readFileSync35(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19728
19729
  } catch {
19729
19730
  try {
19730
- if (true) CLI_VERSION = "0.6.4";
19731
+ if (true) CLI_VERSION = "0.6.6";
19731
19732
  } catch {
19732
19733
  }
19733
19734
  }
@@ -20247,7 +20248,7 @@ var init_telemetry = __esm({
20247
20248
  init_sync_client();
20248
20249
  cliVersion2 = () => {
20249
20250
  try {
20250
- return true ? "0.6.4" : "dev";
20251
+ return true ? "0.6.6" : "dev";
20251
20252
  } catch {
20252
20253
  return "dev";
20253
20254
  }
@@ -20397,12 +20398,16 @@ var init_server_probe = __esm({
20397
20398
  // src/auto-update.ts
20398
20399
  var auto_update_exports = {};
20399
20400
  __export(auto_update_exports, {
20401
+ clearUpdateFailures: () => clearUpdateFailures,
20400
20402
  compareVersions: () => compareVersions,
20401
20403
  executeAutoUpdate: () => executeAutoUpdate,
20404
+ hasGivenUpOn: () => hasGivenUpOn,
20402
20405
  installedVersion: () => installedVersion,
20403
20406
  isAutoUpdateEnabled: () => isAutoUpdateEnabled,
20404
20407
  newerOf: () => newerOf,
20408
+ noteUpdateFailure: () => noteUpdateFailure,
20405
20409
  planAutoUpdate: () => planAutoUpdate,
20410
+ readUpdateState: () => readUpdateState,
20406
20411
  runAutoUpdate: () => runAutoUpdate,
20407
20412
  runningPrefix: () => runningPrefix,
20408
20413
  sweepStaleStaging: () => sweepStaleStaging,
@@ -20411,9 +20416,9 @@ __export(auto_update_exports, {
20411
20416
  import { execSync as execSync4 } from "node:child_process";
20412
20417
  import { readFileSync as readFileSync36 } from "node:fs";
20413
20418
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20414
- import { writeFileSync as writeFileSync9, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync31, statSync as statSync40 } from "node:fs";
20419
+ import { writeFileSync as writeFileSync9, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync31, statSync as statSync40, mkdirSync as mkdirSync12 } from "node:fs";
20415
20420
  import { join as join47, dirname as dirname19, sep as sep3 } from "node:path";
20416
- import { tmpdir as tmpdir3 } from "node:os";
20421
+ import { tmpdir as tmpdir3, homedir as homedir17 } from "node:os";
20417
20422
  import { createHash as createHash10 } from "node:crypto";
20418
20423
  function compareVersions(a, b) {
20419
20424
  const pa = String(a).split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
@@ -20527,16 +20532,25 @@ function runningPrefix(moduleUrl = import.meta.url) {
20527
20532
  const beforeNodeModules = here.slice(0, at);
20528
20533
  return beforeNodeModules.endsWith(`${sep3}lib`) ? beforeNodeModules.slice(0, -4) : beforeNodeModules;
20529
20534
  }
20535
+ function npmInstall(cmd) {
20536
+ try {
20537
+ execSync4(cmd, { stdio: ["ignore", "ignore", "pipe"] });
20538
+ } catch (e) {
20539
+ const err = e;
20540
+ const detail = String(err.stderr ?? "").trim().split("\n").filter((l) => l.trim() && !/^npm (notice|warn)\b/i.test(l)).slice(-4).join(" | ").slice(0, 400);
20541
+ throw new Error(detail ? `${err.message ?? "install failed"} \u2014 ${detail}` : err.message ?? "install failed");
20542
+ }
20543
+ }
20530
20544
  function realInstall(tgz) {
20531
20545
  const prefix = runningPrefix();
20532
20546
  if (prefix) {
20533
- execSync4(`npm install -g --prefix "${prefix}" "${tgz}"`, { stdio: "ignore" });
20547
+ npmInstall(`npm install -g --prefix "${prefix}" "${tgz}"`);
20534
20548
  return;
20535
20549
  }
20536
20550
  try {
20537
- execSync4(`npm install -g "${tgz}"`, { stdio: "ignore" });
20551
+ npmInstall(`npm install -g "${tgz}"`);
20538
20552
  } catch {
20539
- execSync4(`npm install -g --prefix "${process.env.HOME}/.local" "${tgz}"`, { stdio: "ignore" });
20553
+ npmInstall(`npm install -g --prefix "${process.env.HOME}/.local" "${tgz}"`);
20540
20554
  }
20541
20555
  }
20542
20556
  function realRestart(platform2) {
@@ -20544,6 +20558,37 @@ function realRestart(platform2) {
20544
20558
  else if (platform2 === "darwin") execSync4("launchctl kickstart -k gui/$(id -u)/com.chat-recall.watch", { stdio: "ignore", shell: "/bin/bash" });
20545
20559
  else if (platform2 === "win32") execSync4("schtasks /End /TN chat-recall-watch & schtasks /Run /TN chat-recall-watch", { stdio: "ignore" });
20546
20560
  }
20561
+ function updateStatePath() {
20562
+ const dir = process.env.CHAT_RECALL_DATA_DIR || join47(homedir17(), ".chat-recall");
20563
+ return join47(dir, "auto-update-state.json");
20564
+ }
20565
+ function readUpdateState() {
20566
+ try {
20567
+ return JSON.parse(readFileSync36(updateStatePath(), "utf8"));
20568
+ } catch {
20569
+ return {};
20570
+ }
20571
+ }
20572
+ function writeUpdateState(st) {
20573
+ try {
20574
+ const p = updateStatePath();
20575
+ mkdirSync12(dirname19(p), { recursive: true });
20576
+ writeFileSync9(p, JSON.stringify(st), "utf8");
20577
+ } catch {
20578
+ }
20579
+ }
20580
+ function hasGivenUpOn(target, st = readUpdateState()) {
20581
+ return st.target === target && (st.failures ?? 0) >= GIVE_UP_AFTER;
20582
+ }
20583
+ function noteUpdateFailure(target) {
20584
+ const st = readUpdateState();
20585
+ const failures = st.target === target ? (st.failures ?? 0) + 1 : 1;
20586
+ writeUpdateState({ target, failures });
20587
+ return failures;
20588
+ }
20589
+ function clearUpdateFailures() {
20590
+ writeUpdateState({});
20591
+ }
20547
20592
  async function runAutoUpdate(base, authHeaders, ownVersion2, deps) {
20548
20593
  let caps;
20549
20594
  try {
@@ -20566,6 +20611,10 @@ async function runAutoUpdate(base, authHeaders, ownVersion2, deps) {
20566
20611
  }
20567
20612
  return { updated: false, reason: plan.reason };
20568
20613
  }
20614
+ const target = plan.to ?? "";
20615
+ if (target && hasGivenUpOn(target)) {
20616
+ return { updated: false, reason: `gave up installing ${plan.to} after ${GIVE_UP_AFTER} failed attempts \u2014 run \`npm install -g chat-recall\` by hand` };
20617
+ }
20569
20618
  const result = await executeAutoUpdate(plan, {
20570
20619
  download: deps?.download ?? realDownload,
20571
20620
  install: deps?.install ?? realInstall,
@@ -20573,20 +20622,27 @@ async function runAutoUpdate(base, authHeaders, ownVersion2, deps) {
20573
20622
  platform: deps?.platform
20574
20623
  });
20575
20624
  if (!result.updated) {
20576
- try {
20577
- const { reportClientEvent: reportClientEvent3 } = await Promise.resolve().then(() => (init_client_events(), client_events_exports));
20578
- reportClientEvent3("auto_update_failed", { message: `${plan.from} \u2192 ${plan.to}: ${result.reason}` });
20579
- } catch {
20625
+ const failures = target ? noteUpdateFailure(target) : 1;
20626
+ if (failures <= GIVE_UP_AFTER) {
20627
+ try {
20628
+ const { reportClientEvent: reportClientEvent3 } = await Promise.resolve().then(() => (init_client_events(), client_events_exports));
20629
+ const suffix = failures >= GIVE_UP_AFTER ? ` \u2014 giving up after ${failures} attempts, needs a manual \`npm install -g chat-recall\`` : ` (attempt ${failures} of ${GIVE_UP_AFTER})`;
20630
+ reportClientEvent3("auto_update_failed", { message: `${plan.from} \u2192 ${plan.to}: ${result.reason}${suffix}` });
20631
+ } catch {
20632
+ }
20580
20633
  }
20634
+ } else {
20635
+ clearUpdateFailures();
20581
20636
  }
20582
20637
  return result;
20583
20638
  }
20584
- var tarballUrl, reportedStandingSkip;
20639
+ var tarballUrl, GIVE_UP_AFTER, reportedStandingSkip;
20585
20640
  var init_auto_update = __esm({
20586
20641
  "src/auto-update.ts"() {
20587
20642
  "use strict";
20588
20643
  init_http();
20589
20644
  tarballUrl = (base) => `${base.replace(/\/+$/, "")}/install/chat-recall.tgz`;
20645
+ GIVE_UP_AFTER = 3;
20590
20646
  reportedStandingSkip = /* @__PURE__ */ new Set();
20591
20647
  }
20592
20648
  });
@@ -20621,7 +20677,7 @@ __export(sync_client_exports, {
20621
20677
  syncSessions: () => syncSessions
20622
20678
  });
20623
20679
  import { join as join48, dirname as dirname20, basename as basename21 } from "node:path";
20624
- import { readFileSync as readFileSync37, writeFileSync as writeFileSync10, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, rmSync as rmSync6, chmodSync as chmodSync6, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync41 } from "node:fs";
20680
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync4, rmSync as rmSync6, chmodSync as chmodSync6, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync41 } from "node:fs";
20625
20681
  import { tmpdir as tmpdir4, hostname as hostname2 } from "node:os";
20626
20682
  import { createHash as createHash11 } from "node:crypto";
20627
20683
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -20656,7 +20712,7 @@ function classifyError(msg, err) {
20656
20712
  }
20657
20713
  function saveCredentials(c) {
20658
20714
  assertTransportSafe(c.serverUrl);
20659
- mkdirSync12(dirname20(credPath()), { recursive: true });
20715
+ mkdirSync13(dirname20(credPath()), { recursive: true });
20660
20716
  const targets = loadAllCredentials().filter((t) => t.serverUrl !== c.serverUrl);
20661
20717
  targets.unshift(c);
20662
20718
  writeFileSync10(credPath(), JSON.stringify({ targets }, null, 2));
@@ -20751,7 +20807,7 @@ function persistSyncConfig(base, config2) {
20751
20807
  try {
20752
20808
  const all = readPersistedSyncConfigs();
20753
20809
  all[base] = { fetchedAt: Date.now(), config: config2 };
20754
- mkdirSync12(dirname20(syncConfigCachePath()), { recursive: true });
20810
+ mkdirSync13(dirname20(syncConfigCachePath()), { recursive: true });
20755
20811
  writeFileSync10(syncConfigCachePath(), JSON.stringify(all, null, 2));
20756
20812
  } catch {
20757
20813
  }
@@ -22157,7 +22213,7 @@ var init_sync_client = __esm({
22157
22213
  ownVersion = JSON.parse(readFileSync37(join48(dir, "../package.json"), "utf-8")).version || ownVersion;
22158
22214
  } catch {
22159
22215
  try {
22160
- if (true) ownVersion = "0.6.4";
22216
+ if (true) ownVersion = "0.6.6";
22161
22217
  } catch {
22162
22218
  }
22163
22219
  }
@@ -22380,7 +22436,7 @@ __export(install_skills_exports, {
22380
22436
  skillsSourceDir: () => skillsSourceDir,
22381
22437
  uninstallSkills: () => uninstallSkills
22382
22438
  });
22383
- import { readdirSync as readdirSync33, existsSync as existsSync45, mkdirSync as mkdirSync13, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync12, readFileSync as readFileSync40, statSync as statSync42, realpathSync as realpathSync4 } from "fs";
22439
+ import { readdirSync as readdirSync33, existsSync as existsSync45, mkdirSync as mkdirSync14, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync12, readFileSync as readFileSync40, statSync as statSync42, realpathSync as realpathSync4 } from "fs";
22384
22440
  import { createHash as createHash12 } from "crypto";
22385
22441
  import { join as join50, basename as basename22 } from "path";
22386
22442
  function skillsSourceDir() {
@@ -22490,7 +22546,7 @@ function installSkills(opts = {}) {
22490
22546
  skippedUserOwned.push(name);
22491
22547
  continue;
22492
22548
  }
22493
- mkdirSync13(t.dir, { recursive: true });
22549
+ mkdirSync14(t.dir, { recursive: true });
22494
22550
  rmSync7(dest, { recursive: true, force: true });
22495
22551
  cpSync(join50(src, name), dest, { recursive: true });
22496
22552
  writeFileSync12(join50(dest, MARKER), `${stamp}
@@ -22588,14 +22644,14 @@ __export(service_installer_exports, {
22588
22644
  uninstallService: () => uninstallService
22589
22645
  });
22590
22646
  import { execSync as execSync5 } from "child_process";
22591
- import { existsSync as existsSync46, mkdirSync as mkdirSync14, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
22592
- import { homedir as homedir17 } from "os";
22647
+ import { existsSync as existsSync46, mkdirSync as mkdirSync15, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
22648
+ import { homedir as homedir18 } from "os";
22593
22649
  import { join as join51 } from "path";
22594
22650
  import { fileURLToPath as fileURLToPath4 } from "url";
22595
22651
  function resolveDaemonPaths() {
22596
22652
  const watchJs = fileURLToPath4(new URL("./watch.js", import.meta.url));
22597
22653
  const node = process.execPath;
22598
- const dataDir = join51(homedir17(), ".chat-recall");
22654
+ const dataDir = join51(homedir18(), ".chat-recall");
22599
22655
  const logFile = join51(dataDir, "watch.log");
22600
22656
  return { watchJs, node, logFile, dataDir };
22601
22657
  }
@@ -22623,7 +22679,7 @@ function refreshServiceDefinition() {
22623
22679
  if (process.platform !== "linux") return false;
22624
22680
  try {
22625
22681
  const { watchJs, node, logFile } = resolveDaemonPaths();
22626
- const unitPath = join51(homedir17(), ".config", "systemd", "user", "chat-recall-watch.service");
22682
+ const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22627
22683
  if (!existsSync46(unitPath)) return false;
22628
22684
  const current = readFileSync41(unitPath, "utf8");
22629
22685
  if (!current.includes(watchJs)) return false;
@@ -22638,7 +22694,7 @@ function refreshServiceDefinition() {
22638
22694
  }
22639
22695
  function installService() {
22640
22696
  const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
22641
- mkdirSync14(dataDir, { recursive: true });
22697
+ mkdirSync15(dataDir, { recursive: true });
22642
22698
  if (process.platform === "linux") {
22643
22699
  return installSystemd(watchJs, node, logFile);
22644
22700
  }
@@ -22654,10 +22710,10 @@ function installService() {
22654
22710
  );
22655
22711
  }
22656
22712
  function installSystemd(watchJs, node, logFile) {
22657
- const unitDir = join51(homedir17(), ".config", "systemd", "user");
22713
+ const unitDir = join51(homedir18(), ".config", "systemd", "user");
22658
22714
  const unitPath = join51(unitDir, "chat-recall-watch.service");
22659
22715
  const unit = renderSystemdUnit(watchJs, node, logFile);
22660
- mkdirSync14(unitDir, { recursive: true });
22716
+ mkdirSync15(unitDir, { recursive: true });
22661
22717
  writeFileSync13(unitPath, unit);
22662
22718
  try {
22663
22719
  execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
@@ -22729,10 +22785,10 @@ function renderSystemdUnit(watchJs, node, logFile) {
22729
22785
  }
22730
22786
  function installLaunchd(watchJs, node, logFile) {
22731
22787
  const label = "com.chat-recall.watch";
22732
- const plistDir = join51(homedir17(), "Library", "LaunchAgents");
22788
+ const plistDir = join51(homedir18(), "Library", "LaunchAgents");
22733
22789
  const plistPath = join51(plistDir, `${label}.plist`);
22734
22790
  const plist = renderLaunchdPlist(watchJs, node, logFile, label);
22735
- mkdirSync14(plistDir, { recursive: true });
22791
+ mkdirSync15(plistDir, { recursive: true });
22736
22792
  writeFileSync13(plistPath, plist);
22737
22793
  try {
22738
22794
  try {
@@ -22786,7 +22842,7 @@ function uninstallService() {
22786
22842
  removed = true;
22787
22843
  } catch {
22788
22844
  }
22789
- const unitPath = join51(homedir17(), ".config", "systemd", "user", "chat-recall-watch.service");
22845
+ const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22790
22846
  if (existsSync46(unitPath)) {
22791
22847
  rmSync8(unitPath, { force: true });
22792
22848
  removed = true;
@@ -22798,7 +22854,7 @@ function uninstallService() {
22798
22854
  return removed;
22799
22855
  }
22800
22856
  if (process.platform === "darwin") {
22801
- const plistPath = join51(homedir17(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
22857
+ const plistPath = join51(homedir18(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
22802
22858
  try {
22803
22859
  execSync5(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
22804
22860
  } catch {
@@ -23008,6 +23064,118 @@ function resumeCommandFor(sessionId, tool) {
23008
23064
 
23009
23065
  // ../engine/dist/mcp/tools.js
23010
23066
  init_project_resolver();
23067
+
23068
+ // ../engine/dist/core/decision-areas.js
23069
+ var DECISION_AREAS = [
23070
+ "auth",
23071
+ "database",
23072
+ "payments",
23073
+ "api",
23074
+ "frontend",
23075
+ "deploy",
23076
+ "testing",
23077
+ "observability",
23078
+ "security",
23079
+ "pricing",
23080
+ "licensing",
23081
+ "positioning"
23082
+ ];
23083
+ var AREA_SET = new Set(DECISION_AREAS);
23084
+ var AREA_ALIASES = {
23085
+ // auth
23086
+ authentication: "auth",
23087
+ authn: "auth",
23088
+ authz: "auth",
23089
+ authorisation: "auth",
23090
+ authorization: "auth",
23091
+ login: "auth",
23092
+ "sign-in": "auth",
23093
+ signin: "auth",
23094
+ sso: "auth",
23095
+ identity: "auth",
23096
+ // database
23097
+ db: "database",
23098
+ datastore: "database",
23099
+ storage: "database",
23100
+ persistence: "database",
23101
+ orm: "database",
23102
+ migrations: "database",
23103
+ // payments
23104
+ billing: "payments",
23105
+ payment: "payments",
23106
+ checkout: "payments",
23107
+ subscriptions: "payments",
23108
+ // api
23109
+ backend: "api",
23110
+ server: "api",
23111
+ rpc: "api",
23112
+ // frontend
23113
+ ui: "frontend",
23114
+ client: "frontend",
23115
+ web: "frontend",
23116
+ styling: "frontend",
23117
+ css: "frontend",
23118
+ // deploy
23119
+ deployment: "deploy",
23120
+ infra: "deploy",
23121
+ infrastructure: "deploy",
23122
+ hosting: "deploy",
23123
+ ci: "deploy",
23124
+ cd: "deploy",
23125
+ "ci-cd": "deploy",
23126
+ release: "deploy",
23127
+ // testing
23128
+ tests: "testing",
23129
+ test: "testing",
23130
+ qa: "testing",
23131
+ e2e: "testing",
23132
+ // observability
23133
+ monitoring: "observability",
23134
+ logging: "observability",
23135
+ metrics: "observability",
23136
+ alerting: "observability",
23137
+ tracing: "observability",
23138
+ // security
23139
+ secrets: "security",
23140
+ compliance: "security",
23141
+ privacy: "security",
23142
+ // pricing
23143
+ price: "pricing",
23144
+ plans: "pricing",
23145
+ tiers: "pricing",
23146
+ // licensing
23147
+ licence: "licensing",
23148
+ license: "licensing",
23149
+ // positioning
23150
+ marketing: "positioning",
23151
+ branding: "positioning",
23152
+ messaging: "positioning"
23153
+ };
23154
+ function canonArea(raw) {
23155
+ if (!raw)
23156
+ return null;
23157
+ const lowered = raw.trim().toLowerCase();
23158
+ if (!lowered)
23159
+ return null;
23160
+ const slug = lowered.replace(/[\s_/]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
23161
+ if (!slug)
23162
+ return null;
23163
+ if (AREA_SET.has(slug))
23164
+ return slug;
23165
+ if (AREA_ALIASES[slug])
23166
+ return AREA_ALIASES[slug];
23167
+ return slug.slice(0, 40);
23168
+ }
23169
+ function isKnownArea(key2) {
23170
+ return !!key2 && AREA_SET.has(key2);
23171
+ }
23172
+ var ACCOUNT_SCOPE = "*";
23173
+ function decisionSubject(project, area) {
23174
+ const p = (project || "").trim() || ACCOUNT_SCOPE;
23175
+ return `${p}:${area}`;
23176
+ }
23177
+
23178
+ // ../engine/dist/mcp/tools.js
23011
23179
  import { config } from "dotenv";
23012
23180
 
23013
23181
  // ../engine/dist/mcp/diagnostics.js
@@ -23856,10 +24024,20 @@ var RecallDecisionRecordSchema = z.object({
23856
24024
  subject: z.string().describe('What the decision is about (e.g., "chat-recall", "auth strategy")'),
23857
24025
  decision: z.string().describe('The decision itself in plain words (e.g., "use Postgres full-text search as the default backend")'),
23858
24026
  reason: z.string().optional().describe("Why this was decided \u2014 short rationale"),
24027
+ // The key a reversal supersedes on. Without it the subject is free text, so
24028
+ // "auth", "authentication" and "auth setup" are three separate facts and a
24029
+ // later decision never closes an earlier one — see core/decision-areas.ts.
24030
+ area: z.string().optional().describe(`The area this decides, so a later decision about the SAME area supersedes it. Use one of: ${DECISION_AREAS.join(", ")} \u2014 or any short slug for an area not listed. Without it the decision is recorded but nothing it contradicts is closed.`),
24031
+ project: z.string().optional().describe("Project this decision applies to. Omit for an account-wide decision that applies everywhere."),
23859
24032
  importance: z.number().min(1).max(5).optional().default(4).describe("1\u20135; the classifier surfaces 4+ in wake-up context"),
23860
24033
  session_id: z.string().optional().describe("Session this decision was made in (for traceability)"),
23861
24034
  agent_name: z.string().optional().default("agent").describe("Who recorded the decision (for diary linkage)")
23862
24035
  });
24036
+ var RecallDecisionsSchema = z.object({
24037
+ area: z.string().optional().describe("Only this area"),
24038
+ project: z.string().optional().describe("Resolve for this project"),
24039
+ include_candidates: z.boolean().optional().default(false)
24040
+ });
23863
24041
  var RecallAnalyticsSummarySchema = z.object({
23864
24042
  // No required inputs — returns the same summary the dashboard renders.
23865
24043
  view: z.enum(["summary", "patterns"]).optional().default("summary").describe("summary = spend/activity rollup; patterns = behavioural patterns across sessions")
@@ -24008,6 +24186,7 @@ var LEAN_TOOLS = /* @__PURE__ */ new Set([
24008
24186
  "recall_kg_query",
24009
24187
  "recall_kg_add",
24010
24188
  "recall_decision_record",
24189
+ "recall_decisions",
24011
24190
  "recall_diary_write",
24012
24191
  "recall_diary_read",
24013
24192
  // Health, and the one that pays out on day one.
@@ -24904,13 +25083,19 @@ I asking yesterday?" or "what did I tell the agent in this session?".`,
24904
25083
  name: "recall_decision_record",
24905
25084
  description: `Record an explicit decision so it shows up in wake-up context. Writes a KG triple
24906
25085
  (subject \u2192 decided \u2192 decision) plus a diary entry. Use this when you and the user agree on
24907
- something non-obvious that future sessions should remember.`,
25086
+ something non-obvious that future sessions should remember.
25087
+
25088
+ PASS \`area\`. It is the key a reversal supersedes on: recording a new \`auth\` decision closes the
25089
+ previous \`auth\` one and leaves every other area alone. Without it the decision is stored but
25090
+ nothing it contradicts is closed, and both answers stay live forever.`,
24908
25091
  inputSchema: {
24909
25092
  type: "object",
24910
25093
  properties: {
24911
25094
  subject: { type: "string", description: "What the decision is about" },
24912
25095
  decision: { type: "string", description: "The decision itself" },
24913
25096
  reason: { type: "string", description: "Why this was decided" },
25097
+ area: { type: "string", description: `The area this decides, so a later decision on the same area supersedes it. Standard areas: ${DECISION_AREAS.join(", ")}. Any short slug works for an area not listed.` },
25098
+ project: { type: "string", description: "Project this applies to. Omit for an account-wide decision that applies everywhere." },
24914
25099
  importance: { type: "number", minimum: 1, maximum: 5, default: 4, description: "1-5. Only 4 and 5 surface in the wake-up bundle, so reserve those for decisions a future session must not miss." },
24915
25100
  session_id: { type: "string", description: "Session this decision came out of. Pass your own current session id so the decision can be traced back to the conversation that made it." },
24916
25101
  agent_name: { type: "string", default: "agent", description: "Which agent is recording this, so diary entries stay separable per agent." }
@@ -24918,6 +25103,25 @@ something non-obvious that future sessions should remember.`,
24918
25103
  required: ["subject", "decision"]
24919
25104
  }
24920
25105
  },
25106
+ {
25107
+ name: "recall_decisions",
25108
+ description: `What this codebase has already settled \u2014 read it BEFORE choosing a library, a
25109
+ datastore, an auth method or anything else in a decided area, and you will not rebuild what was
25110
+ already ruled out.
25111
+
25112
+ Returns one effective value per area with the cascade already resolved (project beats account;
25113
+ personal preferences fill gaps and never override a team decision), what each value replaced and
25114
+ when, and the session it came from. Also returns \`gaps\` \u2014 areas nobody has decided, where you are
25115
+ free to choose, and where recording the choice afterwards is worth doing.`,
25116
+ inputSchema: {
25117
+ type: "object",
25118
+ properties: {
25119
+ area: { type: "string", description: `Only this area. Standard areas: ${DECISION_AREAS.join(", ")}.` },
25120
+ project: { type: "string", description: "Resolve for this project, so its overrides win over account-wide decisions. Omit for the account-wide view." },
25121
+ include_candidates: { type: "boolean", default: false, description: "Also return unconfirmed guesses the indexer extracted. These are NOT decisions \u2014 never treat one as settled." }
25122
+ }
25123
+ }
25124
+ },
24921
25125
  {
24922
25126
  name: "recall_analytics_summary",
24923
25127
  description: `Cross-tool spend & activity overview. Returns total sessions, total cost,
@@ -27028,8 +27232,10 @@ ${JSON.stringify(r, null, 2)}
27028
27232
  const wal = getWAL();
27029
27233
  wal.log("decision_record", { subject: params.subject, importance: params.importance });
27030
27234
  const confidence = Math.min(1, params.importance / 5);
27235
+ const area = canonArea(params.area);
27236
+ const kgSubject = area ? decisionSubject(params.project, area) : params.subject;
27031
27237
  await remotePost("/api/kg/add", {
27032
- subject: params.subject,
27238
+ subject: kgSubject,
27033
27239
  predicate: "decided",
27034
27240
  object: params.decision,
27035
27241
  confidence,
@@ -27037,11 +27243,12 @@ ${JSON.stringify(r, null, 2)}
27037
27243
  });
27038
27244
  if (params.reason) {
27039
27245
  await remotePost("/api/kg/add", {
27040
- subject: params.subject,
27246
+ subject: kgSubject,
27041
27247
  predicate: "because",
27042
27248
  object: params.reason,
27043
27249
  confidence,
27044
- source_session: params.session_id
27250
+ source_session: params.session_id,
27251
+ supersede: false
27045
27252
  });
27046
27253
  }
27047
27254
  const diaryText = params.reason ? `Decided: ${params.decision}
@@ -27057,12 +27264,58 @@ Subject: ${params.subject}`;
27057
27264
  return {
27058
27265
  content: [{
27059
27266
  type: "text",
27060
- text: `Decision recorded.
27061
- - KG: ${params.subject} \u2192 decided \u2192 ${params.decision}
27062
- - Diary entry: ${diary.id} (importance ${params.importance})`
27267
+ text: [
27268
+ "Decision recorded.",
27269
+ `- KG: ${kgSubject} \u2192 decided \u2192 ${params.decision}`,
27270
+ area ? `- Area: ${area}${isKnownArea(area) ? "" : " (not a standard area \u2014 kept as a slug)"} \xB7 a later decision on this area supersedes this one` : "- No area given, so nothing this contradicts was closed. Pass `area` to make reversals work.",
27271
+ `- Diary entry: ${diary.id} (importance ${params.importance})`
27272
+ ].join("\n")
27063
27273
  }]
27064
27274
  };
27065
27275
  }
27276
+ // ── The decision register ──────────────────────────────────
27277
+ case "recall_decisions": {
27278
+ const p = RecallDecisionsSchema.parse(args ?? {});
27279
+ requireRemote();
27280
+ const r = await remoteGetQS("/api/decisions", {
27281
+ project: p.project,
27282
+ include_candidates: p.include_candidates ? "1" : "0"
27283
+ });
27284
+ const rows = p.area ? r.decisions.filter((d) => d.area === canonArea(p.area)) : r.decisions;
27285
+ const lines = [];
27286
+ lines.push(r.project ? `Decisions for ${r.project}` : "Decisions \u2014 account-wide");
27287
+ if (rows.length === 0) {
27288
+ lines.push("", p.area ? `Nothing decided for "${p.area}". You are free to choose \u2014 record it afterwards with recall_decision_record.` : "Nothing decided yet.");
27289
+ } else {
27290
+ lines.push("");
27291
+ for (const d of rows) {
27292
+ const tags = [
27293
+ d.override ? "OVERRIDES ACCOUNT" : "",
27294
+ d.inherited ? "inherited from account" : "",
27295
+ d.advisory ? "advisory \u2014 a personal preference, not binding" : ""
27296
+ ].filter(Boolean).join(" \xB7 ");
27297
+ lines.push(`${d.area}: ${d.value}${d.since ? ` (since ${d.since})` : ""}${tags ? ` [${tags}]` : ""}`);
27298
+ if (d.why)
27299
+ lines.push(` why: ${d.why}`);
27300
+ const replaced = d.history.filter((h) => !h.current);
27301
+ for (const h of replaced)
27302
+ lines.push(` replaced: ${h.value} (ended ${h.to})`);
27303
+ if (d.source_session)
27304
+ lines.push(` from session ${d.source_session}`);
27305
+ }
27306
+ }
27307
+ if (!p.area && r.gaps.length) {
27308
+ lines.push("", `Not decided: ${r.gaps.map((g) => g.area).join(", ")}`);
27309
+ lines.push("Free to choose in these. Record the choice so the next session inherits it.");
27310
+ }
27311
+ if (p.include_candidates && r.candidates?.length) {
27312
+ lines.push("", "Unconfirmed guesses from indexing \u2014 NOT decisions, do not treat as settled:");
27313
+ for (const c of r.candidates.slice(0, 10)) {
27314
+ lines.push(` ${c.value} (seen ${c.mentions}\xD7${c.last_seen ? `, latest ${c.last_seen}` : ""})`);
27315
+ }
27316
+ }
27317
+ return { content: [{ type: "text", text: lines.join("\n") }] };
27318
+ }
27066
27319
  // ── Analytics summary ──────────────────────────────────────
27067
27320
  case "recall_analytics_summary": {
27068
27321
  const analyticsParams = RecallAnalyticsSummarySchema.parse(args ?? {});
@@ -27808,7 +28061,7 @@ function installCrashGuards() {
27808
28061
  reportClientEvent2("mcp_crash", { message: err.stack || err.message });
27809
28062
  });
27810
28063
  }
27811
- var RELAY_VERSION = true ? "0.6.4" : "0.0.0";
28064
+ var RELAY_VERSION = true ? "0.6.6" : "0.0.0";
27812
28065
  function daemonIdleSecs() {
27813
28066
  const raw = process.env.CHAT_RECALL_DAEMON_IDLE_SECS;
27814
28067
  if (raw === void 0) return 900;
@@ -0,0 +1,13 @@
1
+ export declare function shouldAskForStar(resultCount: number): boolean;
2
+ /** Record that it has been shown. A failure here means it may be shown once
3
+ * more on the next run, which is the harmless direction to fail in. */
4
+ export declare function markStarAsked(): void;
5
+ /**
6
+ * Print the ask and record it. Writes to stderr so a piped stdout carries only
7
+ * search results.
8
+ *
9
+ * `dim` is passed in rather than imported so this module pulls in no colour
10
+ * dependency of its own and a caller can hand it a no-op.
11
+ */
12
+ export declare function askForStar(resultCount: number, dim: (s: string) => string): void;
13
+ //# sourceMappingURL=star-nudge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"star-nudge.d.ts","sourceRoot":"","sources":["../src/star-nudge.ts"],"names":[],"mappings":"AA6CA,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAO7D;AAED;wEACwE;AACxE,wBAAgB,aAAa,IAAI,IAAI,CAMpC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAKhF"}