cc-claw 0.12.1 → 0.12.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.
Files changed (2) hide show
  1. package/dist/cli.js +308 -53
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -72,7 +72,7 @@ var VERSION;
72
72
  var init_version = __esm({
73
73
  "src/version.ts"() {
74
74
  "use strict";
75
- VERSION = true ? "0.12.1" : (() => {
75
+ VERSION = true ? "0.12.2" : (() => {
76
76
  try {
77
77
  return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
78
78
  } catch {
@@ -678,8 +678,12 @@ var init_identity = __esm({
678
678
  // src/reflection/store.ts
679
679
  var store_exports4 = {};
680
680
  __export(store_exports4, {
681
+ advanceReviewSession: () => advanceReviewSession,
681
682
  cleanupBackupFiles: () => cleanupBackupFiles,
682
683
  cleanupReflectionData: () => cleanupReflectionData,
684
+ createReviewSession: () => createReviewSession,
685
+ deleteReviewSession: () => deleteReviewSession,
686
+ getAppliedCountTodayForFile: () => getAppliedCountTodayForFile,
683
687
  getAppliedInsightCountToday: () => getAppliedInsightCountToday,
684
688
  getAppliedInsights: () => getAppliedInsights,
685
689
  getGrowthMetrics: () => getGrowthMetrics,
@@ -688,8 +692,10 @@ __export(store_exports4, {
688
692
  getPendingInsightCount: () => getPendingInsightCount,
689
693
  getPendingInsights: () => getPendingInsights,
690
694
  getReflectionModelConfig: () => getReflectionModelConfig,
695
+ getReflectionSettings: () => getReflectionSettings,
691
696
  getReflectionStatus: () => getReflectionStatus,
692
697
  getRejectedInsights: () => getRejectedInsights,
698
+ getReviewSession: () => getReviewSession,
693
699
  getSignalCountForChat: () => getSignalCountForChat,
694
700
  getUnprocessedSignalCount: () => getUnprocessedSignalCount,
695
701
  getUnprocessedSignals: () => getUnprocessedSignals,
@@ -698,6 +704,7 @@ __export(store_exports4, {
698
704
  logSignal: () => logSignal,
699
705
  markSignalsProcessed: () => markSignalsProcessed,
700
706
  setReflectionModelConfig: () => setReflectionModelConfig,
707
+ setReflectionSettings: () => setReflectionSettings,
701
708
  setReflectionStatus: () => setReflectionStatus,
702
709
  updateInsightEffectiveness: () => updateInsightEffectiveness,
703
710
  updateInsightProposal: () => updateInsightProposal,
@@ -707,16 +714,37 @@ __export(store_exports4, {
707
714
  });
708
715
  import { existsSync, readdirSync, statSync, unlinkSync } from "fs";
709
716
  import { join as join3 } from "path";
710
- function cleanupBackupFiles(ccClawHome) {
711
- const identityDir = join3(ccClawHome, "identity");
712
- if (!existsSync(identityDir)) return;
713
- const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1e3;
714
- for (const file of readdirSync(identityDir)) {
715
- if (!file.includes(".bak.")) continue;
716
- const fullPath = join3(identityDir, file);
717
+ function cleanupBackupFiles(ccClawHome, retentionDays = 7) {
718
+ const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
719
+ const dirs = [
720
+ join3(ccClawHome, "identity"),
721
+ join3(ccClawHome, "workspace", "context")
722
+ ];
723
+ const skillsRoot = join3(ccClawHome, "workspace", "skills");
724
+ try {
725
+ if (existsSync(skillsRoot)) {
726
+ for (const entry of readdirSync(skillsRoot)) {
727
+ const p = join3(skillsRoot, entry);
728
+ try {
729
+ if (statSync(p).isDirectory()) dirs.push(p);
730
+ } catch {
731
+ }
732
+ }
733
+ }
734
+ } catch {
735
+ }
736
+ for (const dir of dirs) {
737
+ if (!existsSync(dir)) continue;
717
738
  try {
718
- const stat2 = statSync(fullPath);
719
- if (stat2.mtimeMs < thirtyDaysAgo) unlinkSync(fullPath);
739
+ for (const file of readdirSync(dir)) {
740
+ if (!file.includes(".bak.")) continue;
741
+ const fullPath = join3(dir, file);
742
+ try {
743
+ const stat2 = statSync(fullPath);
744
+ if (stat2.mtimeMs < cutoffMs) unlinkSync(fullPath);
745
+ } catch {
746
+ }
747
+ }
720
748
  } catch {
721
749
  }
722
750
  }
@@ -792,9 +820,25 @@ function initReflectionTables(db3) {
792
820
  model TEXT,
793
821
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
794
822
  );
823
+
824
+ CREATE TABLE IF NOT EXISTS review_sessions (
825
+ chatId TEXT PRIMARY KEY,
826
+ insightIds TEXT NOT NULL,
827
+ currentIndex INTEGER NOT NULL DEFAULT 0,
828
+ results TEXT NOT NULL DEFAULT '{}',
829
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
830
+ );
831
+
832
+ CREATE TABLE IF NOT EXISTS reflection_settings (
833
+ chatId TEXT PRIMARY KEY,
834
+ perFileCap INTEGER NOT NULL DEFAULT 3,
835
+ backupRetentionDays INTEGER NOT NULL DEFAULT 7,
836
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
837
+ );
795
838
  `);
796
839
  cleanupReflectionData(db3);
797
- cleanupBackupFiles(CC_CLAW_HOME);
840
+ const settings = getReflectionSettings(db3, "global");
841
+ cleanupBackupFiles(CC_CLAW_HOME, settings.backupRetentionDays);
798
842
  }
799
843
  function logSignal(db3, params) {
800
844
  const result = db3.prepare(`
@@ -939,6 +983,13 @@ function getAppliedInsightCountToday(db3, chatId) {
939
983
  `).get(chatId);
940
984
  return row.count;
941
985
  }
986
+ function getAppliedCountTodayForFile(db3, chatId, targetFile) {
987
+ const row = db3.prepare(`
988
+ SELECT COUNT(*) as count FROM insights
989
+ WHERE chatId = ? AND status = 'applied' AND targetFile = ? AND appliedAt >= date('now') AND appliedAt < date('now', '+1 day')
990
+ `).get(chatId, targetFile);
991
+ return row.count;
992
+ }
942
993
  function upsertGrowthMetric(db3, chatId, period, data) {
943
994
  db3.prepare(`
944
995
  INSERT OR REPLACE INTO growth_metrics (chatId, period, corrections, praises, errors, insightsApplied)
@@ -966,6 +1017,58 @@ function setReflectionModelConfig(db3, chatId, mode, backend2, model2) {
966
1017
  VALUES (?, ?, ?, ?)
967
1018
  `).run(chatId, mode, backend2 ?? null, model2 ?? null);
968
1019
  }
1020
+ function createReviewSession(db3, chatId, insightIds) {
1021
+ db3.prepare(`
1022
+ INSERT OR REPLACE INTO review_sessions (chatId, insightIds, currentIndex, results)
1023
+ VALUES (?, ?, 0, '{}')
1024
+ `).run(chatId, JSON.stringify(insightIds));
1025
+ }
1026
+ function getReviewSession(db3, chatId) {
1027
+ const row = db3.prepare(
1028
+ "SELECT * FROM review_sessions WHERE chatId = ?"
1029
+ ).get(chatId);
1030
+ if (!row) return null;
1031
+ return {
1032
+ chatId: row.chatId,
1033
+ insightIds: JSON.parse(row.insightIds),
1034
+ currentIndex: row.currentIndex,
1035
+ results: JSON.parse(row.results),
1036
+ created_at: row.created_at
1037
+ };
1038
+ }
1039
+ function advanceReviewSession(db3, chatId, insightId, action) {
1040
+ const session2 = getReviewSession(db3, chatId);
1041
+ if (!session2) return null;
1042
+ session2.results[insightId] = action;
1043
+ session2.currentIndex = session2.currentIndex + 1;
1044
+ db3.prepare(`
1045
+ UPDATE review_sessions SET currentIndex = ?, results = ? WHERE chatId = ?
1046
+ `).run(session2.currentIndex, JSON.stringify(session2.results), chatId);
1047
+ return session2;
1048
+ }
1049
+ function deleteReviewSession(db3, chatId) {
1050
+ db3.prepare("DELETE FROM review_sessions WHERE chatId = ?").run(chatId);
1051
+ }
1052
+ function getReflectionSettings(db3, chatId) {
1053
+ const row = db3.prepare(
1054
+ "SELECT perFileCap, backupRetentionDays FROM reflection_settings WHERE chatId = ?"
1055
+ ).get(chatId);
1056
+ return {
1057
+ perFileCap: row?.perFileCap ?? 3,
1058
+ backupRetentionDays: row?.backupRetentionDays ?? 7
1059
+ };
1060
+ }
1061
+ function setReflectionSettings(db3, chatId, settings) {
1062
+ const current = getReflectionSettings(db3, chatId);
1063
+ db3.prepare(`
1064
+ INSERT OR REPLACE INTO reflection_settings (chatId, perFileCap, backupRetentionDays)
1065
+ VALUES (?, ?, ?)
1066
+ `).run(
1067
+ chatId,
1068
+ settings.perFileCap ?? current.perFileCap,
1069
+ settings.backupRetentionDays ?? current.backupRetentionDays
1070
+ );
1071
+ }
969
1072
  function cleanupReflectionData(db3) {
970
1073
  db3.prepare(`
971
1074
  DELETE FROM feedback_signals WHERE created_at < datetime('now', '-90 days')
@@ -976,6 +1079,9 @@ function cleanupReflectionData(db3) {
976
1079
  db3.prepare(`
977
1080
  DELETE FROM growth_metrics WHERE created_at < datetime('now', '-365 days')
978
1081
  `).run();
1082
+ db3.prepare(`
1083
+ DELETE FROM review_sessions WHERE created_at < datetime('now', '-1 day')
1084
+ `).run();
979
1085
  }
980
1086
  var init_store4 = __esm({
981
1087
  "src/reflection/store.ts"() {
@@ -6689,6 +6795,7 @@ Rules:
6689
6795
  - WHY field max 2 sentences
6690
6796
  - Only propose changes you are confident about (confidence >= 0.6)
6691
6797
  - Never re-propose previously rejected insights
6798
+ - Do not propose more than 3 changes to the same target file in a single analysis (spread across files to avoid drift)
6692
6799
 
6693
6800
  Valid categories:
6694
6801
  ${categoryList}`);
@@ -7073,7 +7180,7 @@ __export(apply_exports, {
7073
7180
  isTargetAllowed: () => isTargetAllowed,
7074
7181
  rollbackInsight: () => rollbackInsight
7075
7182
  });
7076
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync3 } from "fs";
7183
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync3, readdirSync as readdirSync6, unlinkSync as unlinkSync4 } from "fs";
7077
7184
  import { join as join11, dirname as dirname2 } from "path";
7078
7185
  function isTargetAllowed(relativePath) {
7079
7186
  if (relativePath.includes("..")) return false;
@@ -7124,6 +7231,21 @@ function applyDiff(original, diff, action) {
7124
7231
  warn(`[reflection/apply] Unknown diff action "${action}", returning original`);
7125
7232
  return original;
7126
7233
  }
7234
+ function pruneBackups(absolutePath) {
7235
+ const dir = dirname2(absolutePath);
7236
+ const baseName = absolutePath.split("/").pop() ?? "";
7237
+ try {
7238
+ const backups = readdirSync6(dir).filter((f) => f.startsWith(baseName + ".bak.")).sort().map((f) => join11(dir, f));
7239
+ while (backups.length > MAX_BACKUPS_PER_FILE) {
7240
+ const oldest = backups.shift();
7241
+ try {
7242
+ unlinkSync4(oldest);
7243
+ } catch {
7244
+ }
7245
+ }
7246
+ } catch {
7247
+ }
7248
+ }
7127
7249
  async function applyInsight(insightId) {
7128
7250
  const db3 = getDb();
7129
7251
  const insight = getInsightById(db3, insightId);
@@ -7153,11 +7275,13 @@ async function applyInsight(insightId) {
7153
7275
  }
7154
7276
  }
7155
7277
  const chatId = insight.chatId ?? "global";
7156
- const appliedToday = getAppliedInsightCountToday(db3, chatId);
7157
- if (appliedToday >= DAILY_APPLY_CAP) {
7278
+ const settings = getReflectionSettings(db3, chatId);
7279
+ const perFileCap = settings.perFileCap;
7280
+ const appliedTodayForFile = getAppliedCountTodayForFile(db3, chatId, insight.targetFile);
7281
+ if (appliedTodayForFile >= perFileCap) {
7158
7282
  return {
7159
7283
  success: false,
7160
- message: `Daily apply cap reached (${appliedToday}/${DAILY_APPLY_CAP}). Try again tomorrow.`
7284
+ message: `Reflection cap reached for ${insight.targetFile} (${appliedTodayForFile}/${perFileCap} today). Try again tomorrow.`
7161
7285
  };
7162
7286
  }
7163
7287
  let original = "";
@@ -7175,6 +7299,7 @@ async function applyInsight(insightId) {
7175
7299
  }
7176
7300
  if (original) {
7177
7301
  writeFileSync3(backupPath, original, "utf-8");
7302
+ pruneBackups(absolutePath);
7178
7303
  }
7179
7304
  const newContent = applyDiff(original, insight.proposedDiff, insight.proposedAction);
7180
7305
  writeFileSync3(absolutePath, newContent, "utf-8");
@@ -7295,7 +7420,7 @@ function computeLineDrift(baseline, absolutePath) {
7295
7420
  const changed = totalBaseline - unchanged;
7296
7421
  return changed / totalBaseline;
7297
7422
  }
7298
- var ALLOWED_TARGETS, ALLOWED_PREFIXES, SOUL_LINE_CAP, DAILY_APPLY_CAP;
7423
+ var ALLOWED_TARGETS, ALLOWED_PREFIXES, SOUL_LINE_CAP, MAX_BACKUPS_PER_FILE;
7299
7424
  var init_apply = __esm({
7300
7425
  "src/reflection/apply.ts"() {
7301
7426
  "use strict";
@@ -7314,7 +7439,7 @@ var init_apply = __esm({
7314
7439
  "workspace/skills/"
7315
7440
  ];
7316
7441
  SOUL_LINE_CAP = 100;
7317
- DAILY_APPLY_CAP = 3;
7442
+ MAX_BACKUPS_PER_FILE = 3;
7318
7443
  }
7319
7444
  });
7320
7445
 
@@ -8132,6 +8257,21 @@ data: ${JSON.stringify(data)}
8132
8257
  return jsonResponse(res, { error: errorMessage(err) }, 400);
8133
8258
  }
8134
8259
  }
8260
+ if (url.pathname === "/api/evolve/settings" && req.method === "POST") {
8261
+ try {
8262
+ const body = JSON.parse(await readBody(req));
8263
+ const { setReflectionSettings: setReflectionSettings2, getReflectionSettings: getReflectionSettings2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
8264
+ const chatId = body.chatId || (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim() || "default";
8265
+ const updates = {};
8266
+ if (body.perFileCap !== void 0) updates.perFileCap = body.perFileCap;
8267
+ if (body.backupRetentionDays !== void 0) updates.backupRetentionDays = body.backupRetentionDays;
8268
+ setReflectionSettings2(getDb(), chatId, updates);
8269
+ const current = getReflectionSettings2(getDb(), chatId);
8270
+ return jsonResponse(res, { success: true, ...current });
8271
+ } catch (err) {
8272
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8273
+ }
8274
+ }
8135
8275
  if (url.pathname === "/" || url.pathname === "/index.html") {
8136
8276
  return htmlResponse(res, DASHBOARD_HTML.replace("__TOKEN__", DASHBOARD_TOKEN));
8137
8277
  }
@@ -9362,11 +9502,13 @@ __export(propose_exports, {
9362
9502
  buildEvolveOnboardingKeyboard: () => buildEvolveOnboardingKeyboard,
9363
9503
  buildModelKeyboard: () => buildModelKeyboard,
9364
9504
  buildProposalKeyboard: () => buildProposalKeyboard,
9505
+ buildReviewCompleteMessage: () => buildReviewCompleteMessage,
9365
9506
  buildUndoKeyboard: () => buildUndoKeyboard,
9366
9507
  formatDiffCodeBlock: () => formatDiffCodeBlock,
9367
9508
  formatGrowthReport: () => formatGrowthReport,
9368
9509
  formatNightlySummary: () => formatNightlySummary,
9369
- formatProposalCard: () => formatProposalCard
9510
+ formatProposalCard: () => formatProposalCard,
9511
+ formatProposalCardWithProgress: () => formatProposalCardWithProgress
9370
9512
  });
9371
9513
  function pct(ratio) {
9372
9514
  return `${Math.round(ratio * 100)}%`;
@@ -9387,6 +9529,28 @@ function formatProposalCard(params) {
9387
9529
  }
9388
9530
  return lines.join("\n");
9389
9531
  }
9532
+ function formatProposalCardWithProgress(params, index, total) {
9533
+ const progress = `\u{1F4CB} Proposal ${index + 1} of ${total}`;
9534
+ const separator = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
9535
+ const card = formatProposalCard(params);
9536
+ return `${progress}
9537
+ ${separator}
9538
+ ${card}`;
9539
+ }
9540
+ function buildReviewCompleteMessage(results) {
9541
+ const values = Object.values(results);
9542
+ const applied = values.filter((v) => v === "applied").length;
9543
+ const rejected = values.filter((v) => v === "rejected").length;
9544
+ const skipped = values.filter((v) => v === "skipped").length;
9545
+ const total = values.length;
9546
+ const parts = [];
9547
+ if (applied > 0) parts.push(`${applied} applied`);
9548
+ if (rejected > 0) parts.push(`${rejected} rejected`);
9549
+ if (skipped > 0) parts.push(`${skipped} skipped`);
9550
+ return `\u2705 Review complete \u2014 ${total} proposal${total === 1 ? "" : "s"}: ${parts.join(", ")}
9551
+
9552
+ Skipped proposals will appear in your next review.`;
9553
+ }
9390
9554
  function formatNightlySummary(insights) {
9391
9555
  const count = insights.length;
9392
9556
  const header2 = `Nightly Reflection \u2014 ${count} proposal${count === 1 ? "" : "s"} ready`;
@@ -9430,7 +9594,10 @@ function buildProposalKeyboard(insightId, category) {
9430
9594
  return [
9431
9595
  [
9432
9596
  { label: "Show Diff", data: `evolve:diff:${insightId}`, style: "primary" },
9433
- { label: "Discuss", data: `evolve:discuss:${insightId}` },
9597
+ { label: "Discuss", data: `evolve:discuss:${insightId}` }
9598
+ ],
9599
+ [
9600
+ { label: "Skip \u23ED", data: `evolve:skip:${insightId}` },
9434
9601
  { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
9435
9602
  ]
9436
9603
  ];
@@ -9438,7 +9605,10 @@ function buildProposalKeyboard(insightId, category) {
9438
9605
  return [
9439
9606
  [
9440
9607
  { label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
9441
- { label: "Discuss", data: `evolve:discuss:${insightId}` },
9608
+ { label: "Discuss", data: `evolve:discuss:${insightId}` }
9609
+ ],
9610
+ [
9611
+ { label: "Skip \u23ED", data: `evolve:skip:${insightId}` },
9442
9612
  { label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
9443
9613
  ]
9444
9614
  ];
@@ -9959,7 +10129,7 @@ var init_wrap_backend = __esm({
9959
10129
  });
9960
10130
 
9961
10131
  // src/agents/runners/config-loader.ts
9962
- import { readFileSync as readFileSync8, readdirSync as readdirSync6, existsSync as existsSync14, mkdirSync as mkdirSync5, watchFile, unwatchFile } from "fs";
10132
+ import { readFileSync as readFileSync8, readdirSync as readdirSync7, existsSync as existsSync14, mkdirSync as mkdirSync5, watchFile, unwatchFile } from "fs";
9963
10133
  import { join as join13 } from "path";
9964
10134
  import { execFileSync } from "child_process";
9965
10135
  function resolveExecutable(config2) {
@@ -10113,7 +10283,7 @@ function loadAllRunnerConfigs() {
10113
10283
  mkdirSync5(RUNNERS_PATH, { recursive: true });
10114
10284
  return [];
10115
10285
  }
10116
- const files = readdirSync6(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
10286
+ const files = readdirSync7(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
10117
10287
  const configs = [];
10118
10288
  for (const file of files) {
10119
10289
  const config2 = loadRunnerConfig(join13(RUNNERS_PATH, file));
@@ -10144,7 +10314,7 @@ function watchRunnerConfigs(onChange) {
10144
10314
  watchedFiles.delete(prev);
10145
10315
  }
10146
10316
  }
10147
- const files = readdirSync6(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
10317
+ const files = readdirSync7(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
10148
10318
  for (const file of files) {
10149
10319
  const fullPath = join13(RUNNERS_PATH, file);
10150
10320
  if (watchedFiles.has(fullPath)) continue;
@@ -13288,6 +13458,28 @@ function stopAllSideQuests(chatId) {
13288
13458
  }
13289
13459
  }
13290
13460
  }
13461
+ async function sendCurrentProposal(chatId, channel) {
13462
+ const { getReviewSession: getReviewSession2, getInsightById: getInsightById2, deleteReviewSession: deleteReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
13463
+ const { formatProposalCardWithProgress: formatProposalCardWithProgress2, buildProposalKeyboard: buildProposalKeyboard2, buildReviewCompleteMessage: buildReviewCompleteMessage2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
13464
+ const session2 = getReviewSession2(getDb(), chatId);
13465
+ if (!session2) return;
13466
+ if (session2.currentIndex >= session2.insightIds.length) {
13467
+ const summary = buildReviewCompleteMessage2(session2.results);
13468
+ deleteReviewSession2(getDb(), chatId);
13469
+ await channel.sendText(chatId, summary, { parseMode: "plain" });
13470
+ return;
13471
+ }
13472
+ const insightId = session2.insightIds[session2.currentIndex];
13473
+ const insight = getInsightById2(getDb(), insightId);
13474
+ if (!insight || insight.status !== "pending") {
13475
+ const { advanceReviewSession: advanceReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
13476
+ advanceReviewSession2(getDb(), chatId, insightId, "skipped");
13477
+ return sendCurrentProposal(chatId, channel);
13478
+ }
13479
+ const card = formatProposalCardWithProgress2(insight, session2.currentIndex, session2.insightIds.length);
13480
+ const kb = buildProposalKeyboard2(insight.id, insight.category);
13481
+ await channel.sendKeyboard(chatId, card, kb);
13482
+ }
13291
13483
  async function handleResponseExhaustion(responseText, chatId, msg, channel) {
13292
13484
  const raw = responseText.replace(/\n\n🧠 \[.+$/, "").trim();
13293
13485
  if (raw.length > 300 || !isExhaustedMessage(raw)) return false;
@@ -14915,22 +15107,16 @@ Message: "${testMsg}"`, { parseMode: "plain" });
14915
15107
  await channel.sendText(chatId, "Step 2/3: Analyzing for insights...", { parseMode: "plain" });
14916
15108
  try {
14917
15109
  const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
14918
- const { formatProposalCard: formatProposalCard2, buildProposalKeyboard: buildProposalKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
14919
15110
  const insights = await runAnalysis2(chatId, { force: true });
14920
15111
  if (insights.length === 0) {
14921
15112
  await channel.sendText(chatId, "Step 3/3: No actionable improvements found in this session.", { parseMode: "plain" });
14922
15113
  } else {
14923
- await channel.sendText(chatId, `Step 3/3: Found ${insights.length} proposal(s).`, { parseMode: "plain" });
14924
- const { getPendingInsights: getPendingInsights2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
15114
+ await channel.sendText(chatId, `Step 3/3: Found ${insights.length} proposal(s). Let's review them one by one.`, { parseMode: "plain" });
15115
+ const { getPendingInsights: getPendingInsights2, createReviewSession: createReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14925
15116
  const pending = getPendingInsights2(getDb(), chatId);
14926
- for (const insight of pending.slice(0, 5)) {
14927
- const card = formatProposalCard2(insight);
14928
- const kb = buildProposalKeyboard2(insight.id, insight.category);
14929
- await channel.sendKeyboard(chatId, card, kb);
14930
- }
14931
- if (insights.length > 5) {
14932
- await channel.sendText(chatId, `${insights.length - 5} more proposals. Use /evolve to review all.`, { parseMode: "plain" });
14933
- }
15117
+ const insightIds = pending.slice(0, 5).map((p) => p.id);
15118
+ createReviewSession2(getDb(), chatId, insightIds);
15119
+ await sendCurrentProposal(chatId, channel);
14934
15120
  }
14935
15121
  } catch (e) {
14936
15122
  await channel.sendText(chatId, `Analysis failed: ${e}. You can review any pending proposals with /evolve.`, { parseMode: "plain" });
@@ -16677,21 +16863,16 @@ Result: ${task.result.slice(0, 500)}` : ""
16677
16863
  break;
16678
16864
  }
16679
16865
  case "review": {
16680
- const { getPendingInsights: getPendingInsights2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16681
- const { formatProposalCard: formatProposalCard2, buildProposalKeyboard: buildProposalKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16866
+ const { getPendingInsights: getPendingInsights2, createReviewSession: createReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16682
16867
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16683
16868
  const pending = getPendingInsights2(getDb2(), chatId);
16684
16869
  if (pending.length === 0) {
16685
16870
  await channel.sendText(chatId, "No pending proposals.", { parseMode: "plain" });
16686
16871
  } else {
16687
- for (const insight of pending.slice(0, 5)) {
16688
- const card = formatProposalCard2(insight);
16689
- const kb = buildProposalKeyboard2(insight.id, insight.category);
16690
- await channel.sendKeyboard(chatId, card, kb);
16691
- }
16692
- if (pending.length > 5) {
16693
- await channel.sendText(chatId, `${pending.length - 5} more proposals. Run /evolve again to see next batch.`, { parseMode: "plain" });
16694
- }
16872
+ const insightIds = pending.slice(0, 5).map((p) => p.id);
16873
+ createReviewSession2(getDb2(), chatId, insightIds);
16874
+ await channel.sendText(chatId, `${pending.length} proposal(s) ready. Let's review them one by one.`, { parseMode: "plain" });
16875
+ await sendCurrentProposal(chatId, channel);
16695
16876
  }
16696
16877
  break;
16697
16878
  }
@@ -16709,10 +16890,16 @@ Result: ${task.result.slice(0, 500)}` : ""
16709
16890
  const { applyInsight: applyInsight2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
16710
16891
  const result = await applyInsight2(parseInt(idStr, 10));
16711
16892
  await channel.sendText(chatId, result.message, { parseMode: "plain" });
16893
+ const { advanceReviewSession: arAdvance } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16894
+ arAdvance(getDb(), chatId, parseInt(idStr, 10), "applied");
16895
+ await sendCurrentProposal(chatId, channel);
16712
16896
  break;
16713
16897
  }
16714
16898
  case "skip": {
16715
16899
  await channel.sendText(chatId, "Skipped \u2014 will show again next review.", { parseMode: "plain" });
16900
+ const { advanceReviewSession: skAdvance } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16901
+ skAdvance(getDb(), chatId, parseInt(idStr, 10), "skipped");
16902
+ await sendCurrentProposal(chatId, channel);
16716
16903
  break;
16717
16904
  }
16718
16905
  case "discuss": {
@@ -16784,10 +16971,10 @@ Result: ${task.result.slice(0, 500)}` : ""
16784
16971
  join19(homedir6(), ".cc-claw", "workspace", "skills")
16785
16972
  ];
16786
16973
  try {
16787
- const { readdirSync: readdirSync7, statSync: statSync9 } = await import("fs");
16974
+ const { readdirSync: readdirSync8, statSync: statSync9 } = await import("fs");
16788
16975
  for (const dir of skillDirs) {
16789
16976
  if (!existsSync19(dir)) continue;
16790
- for (const entry of readdirSync7(dir)) {
16977
+ for (const entry of readdirSync8(dir)) {
16791
16978
  if (statSync9(join19(dir, entry)).isDirectory()) {
16792
16979
  targets.push({ label: `skills/${entry}`, path: `workspace/skills/${entry}/SKILL.md` });
16793
16980
  }
@@ -16862,14 +17049,20 @@ Pick a different file for this change. Identity files (SOUL/USER) shape personal
16862
17049
  break;
16863
17050
  }
16864
17051
  case "discuss-back": {
16865
- const { getInsightById: bkIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16866
- const { formatProposalCard: bkCard, buildProposalKeyboard: bkKb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
17052
+ const { getInsightById: bkIns, getReviewSession: bkSession } = await Promise.resolve().then(() => (init_store4(), store_exports4));
17053
+ const { formatProposalCardWithProgress: formatProposalCardWithProgress2, formatProposalCard: bkCardFn, buildProposalKeyboard: bkKb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16867
17054
  const { getDb: bkDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16868
17055
  const bkInsight = bkIns(bkDb(), parseInt(idStr, 10));
16869
17056
  if (bkInsight && bkInsight.status === "pending") {
16870
- const card = bkCard(bkInsight);
17057
+ const session2 = bkSession(bkDb(), chatId);
16871
17058
  const kb = bkKb(bkInsight.id, bkInsight.category);
16872
- await channel.sendKeyboard(chatId, card, kb);
17059
+ if (session2) {
17060
+ const card = formatProposalCardWithProgress2(bkInsight, session2.currentIndex, session2.insightIds.length);
17061
+ await channel.sendKeyboard(chatId, card, kb);
17062
+ } else {
17063
+ const card = bkCardFn(bkInsight);
17064
+ await channel.sendKeyboard(chatId, card, kb);
17065
+ }
16873
17066
  }
16874
17067
  break;
16875
17068
  }
@@ -16878,6 +17071,9 @@ Pick a different file for this change. Identity files (SOUL/USER) shape personal
16878
17071
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16879
17072
  updateInsightStatus2(getDb2(), parseInt(idStr, 10), "rejected");
16880
17073
  await channel.sendText(chatId, "Rejected. Won't propose similar changes.", { parseMode: "plain" });
17074
+ const { advanceReviewSession: rjAdvance } = await Promise.resolve().then(() => (init_store4(), store_exports4));
17075
+ rjAdvance(getDb2(), chatId, parseInt(idStr, 10), "rejected");
17076
+ await sendCurrentProposal(chatId, channel);
16881
17077
  break;
16882
17078
  }
16883
17079
  case "stats": {
@@ -18862,7 +19058,7 @@ __export(service_exports, {
18862
19058
  serviceStatus: () => serviceStatus,
18863
19059
  uninstallService: () => uninstallService
18864
19060
  });
18865
- import { existsSync as existsSync24, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4 } from "fs";
19061
+ import { existsSync as existsSync24, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7, unlinkSync as unlinkSync5 } from "fs";
18866
19062
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
18867
19063
  import { homedir as homedir8, platform } from "os";
18868
19064
  import { join as join23, dirname as dirname4 } from "path";
@@ -18959,7 +19155,7 @@ function uninstallMacOS() {
18959
19155
  execFileSync2("launchctl", ["unload", PLIST_PATH]);
18960
19156
  } catch {
18961
19157
  }
18962
- unlinkSync4(PLIST_PATH);
19158
+ unlinkSync5(PLIST_PATH);
18963
19159
  console.log(" Service uninstalled.");
18964
19160
  }
18965
19161
  function formatUptime(seconds) {
@@ -19047,7 +19243,7 @@ function uninstallLinux() {
19047
19243
  execFileSync2("systemctl", ["--user", "disable", "cc-claw"]);
19048
19244
  } catch {
19049
19245
  }
19050
- unlinkSync4(UNIT_PATH);
19246
+ unlinkSync5(UNIT_PATH);
19051
19247
  execFileSync2("systemctl", ["--user", "daemon-reload"]);
19052
19248
  console.log(" Service uninstalled.");
19053
19249
  }
@@ -22136,6 +22332,7 @@ __export(evolve_exports, {
22136
22332
  evolveOff: () => evolveOff,
22137
22333
  evolveOn: () => evolveOn,
22138
22334
  evolveReject: () => evolveReject,
22335
+ evolveSettings: () => evolveSettings,
22139
22336
  evolveStats: () => evolveStats,
22140
22337
  evolveStatus: () => evolveStatus,
22141
22338
  evolveUndo: () => evolveUndo
@@ -22492,6 +22689,60 @@ async function evolveHistory(globalOpts, opts) {
22492
22689
  return lines.join("\n");
22493
22690
  });
22494
22691
  }
22692
+ async function evolveSettings(globalOpts, opts) {
22693
+ ensureDb3();
22694
+ const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
22695
+ const chatId = resolveChatId(globalOpts);
22696
+ const hasUpdates = opts.perFileCap !== void 0 || opts.backupRetentionDays !== void 0;
22697
+ if (hasUpdates) {
22698
+ await ensureDaemon();
22699
+ const { apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
22700
+ const body = {};
22701
+ if (opts.perFileCap !== void 0) {
22702
+ const val = parseInt(opts.perFileCap, 10);
22703
+ if (isNaN(val) || val < 1) {
22704
+ outputError("INVALID_VALUE", "per-file-cap must be a positive integer.");
22705
+ process.exit(1);
22706
+ }
22707
+ body.perFileCap = val;
22708
+ }
22709
+ if (opts.backupRetentionDays !== void 0) {
22710
+ const val = parseInt(opts.backupRetentionDays, 10);
22711
+ if (isNaN(val) || val < 1) {
22712
+ outputError("INVALID_VALUE", "backup-retention-days must be a positive integer.");
22713
+ process.exit(1);
22714
+ }
22715
+ body.backupRetentionDays = val;
22716
+ }
22717
+ const res = await apiPost2("/api/evolve/settings", { chatId, ...body });
22718
+ if (res.ok) {
22719
+ output(res.data, () => `
22720
+ ${success("Reflection settings updated.")}
22721
+ `);
22722
+ } else {
22723
+ outputError("SETTINGS_FAILED", `Failed: ${JSON.stringify(res.data)}`);
22724
+ process.exit(1);
22725
+ }
22726
+ } else {
22727
+ const readDb = openDatabaseReadOnly2();
22728
+ const { getReflectionSettings: getReflectionSettings2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
22729
+ const settings = getReflectionSettings2(readDb, chatId);
22730
+ readDb.close();
22731
+ output(settings, () => {
22732
+ const lines = [
22733
+ "",
22734
+ divider("Reflection Settings"),
22735
+ "",
22736
+ kvLine("Per-file cap", `${settings.perFileCap} changes/day per file`),
22737
+ kvLine("Backup retention", `${settings.backupRetentionDays} days`),
22738
+ "",
22739
+ muted("Update with: cc-claw evolve settings --per-file-cap <n> --backup-retention-days <n>"),
22740
+ ""
22741
+ ];
22742
+ return lines.join("\n");
22743
+ });
22744
+ }
22745
+ }
22495
22746
  var init_evolve = __esm({
22496
22747
  "src/cli/commands/evolve.ts"() {
22497
22748
  "use strict";
@@ -23407,6 +23658,10 @@ evolve.command("history").description("Insight history").option("--status <statu
23407
23658
  const { evolveHistory: evolveHistory2 } = await Promise.resolve().then(() => (init_evolve(), evolve_exports));
23408
23659
  await evolveHistory2(program.opts(), opts);
23409
23660
  });
23661
+ evolve.command("settings").description("View or update reflection settings").option("--per-file-cap <n>", "Max applies per file per day").option("--backup-retention-days <n>", "Days to keep backup files").action(async (opts) => {
23662
+ const { evolveSettings: evolveSettings2 } = await Promise.resolve().then(() => (init_evolve(), evolve_exports));
23663
+ await evolveSettings2(program.opts(), opts);
23664
+ });
23410
23665
  program.command("start", { hidden: true }).description("Run the bot in the foreground (use 'service start' for background daemon)").action(async () => {
23411
23666
  await Promise.resolve().then(() => (init_index(), index_exports));
23412
23667
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "CC-Claw: Personal AI assistant on Telegram — multi-backend (Claude, Gemini, Codex, Cursor), sub-agent orchestration, MCP management",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",