clementine-agent 1.1.28 → 1.1.29

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/index.js +163 -8
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -3862,20 +3862,175 @@ const siCmd = program
3862
3862
  .description('Manage Clementine self-improvement');
3863
3863
  siCmd
3864
3864
  .command('status')
3865
- .description('Show self-improvement state and baseline metrics')
3866
- .action(async () => {
3865
+ .description('Show self-improvement health last cycle, infra errors, per-agent runs, recent activity')
3866
+ .option('--json', 'Emit machine-readable JSON')
3867
+ .action(async (opts) => {
3868
+ const BOLD = '\x1b[1m';
3869
+ const DIM = '\x1b[0;90m';
3870
+ const GREEN = '\x1b[0;32m';
3871
+ const YELLOW = '\x1b[1;33m';
3872
+ const RED = '\x1b[0;31m';
3873
+ const CYAN = '\x1b[0;36m';
3874
+ const RESET = '\x1b[0m';
3867
3875
  try {
3876
+ process.env.CLEMENTINE_HOME = BASE_DIR;
3868
3877
  const { SelfImproveLoop } = await import('../agent/self-improve.js');
3869
3878
  const { PersonalAssistant } = await import('../agent/assistant.js');
3870
3879
  const assistant = new PersonalAssistant();
3871
3880
  const loop = new SelfImproveLoop(assistant);
3872
3881
  const state = loop.loadState();
3873
- const m = state.baselineMetrics;
3874
- console.log(`Status: ${state.status}`);
3875
- console.log(`Last run: ${state.lastRunAt || 'never'}`);
3876
- console.log(`Total experiments: ${state.totalExperiments}`);
3877
- console.log(`Pending approvals: ${state.pendingApprovals}`);
3878
- console.log(`Baseline Feedback: ${(m.feedbackPositiveRatio * 100).toFixed(0)}% positive, Cron: ${(m.cronSuccessRate * 100).toFixed(0)}% success, Quality: ${m.avgResponseQuality.toFixed(2)}`);
3882
+ const log = loop.loadExperimentLog();
3883
+ const pending = loop.getPendingChanges();
3884
+ // Compute "last successful cycle" — most recent log entry that wasn't
3885
+ // a plateau record or pure infra failure. Different from lastRunAt
3886
+ // (which moves on every attempt, even crashed ones).
3887
+ const lastSuccessful = [...log].reverse().find(e => e.area !== 'soul' || e.hypothesis !== 'No new hypothesis — diversity constraint exhausted');
3888
+ const nowMs = Date.now();
3889
+ const formatAge = (iso) => {
3890
+ if (!iso)
3891
+ return 'never';
3892
+ const ms = nowMs - Date.parse(iso);
3893
+ const h = Math.floor(ms / 3_600_000);
3894
+ if (h < 1)
3895
+ return `${Math.floor(ms / 60_000)}m ago`;
3896
+ if (h < 48)
3897
+ return `${h}h ago`;
3898
+ return `${Math.floor(h / 24)}d ago`;
3899
+ };
3900
+ // Auto-applied count over the last 7 days = experiments with status 'approved'
3901
+ const since7d = nowMs - 7 * 86_400_000;
3902
+ const autoAppliedRecent = log.filter(e => e.approvalStatus === 'approved' && Date.parse(e.startedAt) >= since7d).length;
3903
+ // Per-agent SI runs from heartbeat state file.
3904
+ const hbStateFile = path.join(BASE_DIR, '.heartbeat_state.json');
3905
+ let perAgentRuns = {};
3906
+ try {
3907
+ if (existsSync(hbStateFile)) {
3908
+ const hb = JSON.parse(readFileSync(hbStateFile, 'utf-8'));
3909
+ perAgentRuns = (hb.lastAgentSiRuns ?? {});
3910
+ }
3911
+ }
3912
+ catch { /* non-fatal */ }
3913
+ if (opts.json) {
3914
+ console.log(JSON.stringify({
3915
+ state,
3916
+ lastSuccessfulAt: lastSuccessful?.startedAt ?? null,
3917
+ autoAppliedLast7d: autoAppliedRecent,
3918
+ pendingCount: pending.length,
3919
+ perAgentRuns,
3920
+ recent: log.slice(-5).reverse(),
3921
+ }, null, 2));
3922
+ return;
3923
+ }
3924
+ // ── Header ─────────────────────────────────────────────────────
3925
+ console.log();
3926
+ console.log(` ${BOLD}Self-improve loop${RESET}`);
3927
+ console.log(` ${DIM}Status: ${RESET}${state.status}`);
3928
+ console.log(` ${DIM}Last attempted: ${RESET}${state.lastRunAt || 'never'} ${DIM}(${formatAge(state.lastRunAt)})${RESET}`);
3929
+ // Stalled-loop warning: if we have a lastRunAt but no successful cycle
3930
+ // in 36+ hours, surface red. That's the visibility gap from pillar #4.
3931
+ const lastSuccAt = lastSuccessful?.startedAt;
3932
+ const hoursSinceSuccess = lastSuccAt ? (nowMs - Date.parse(lastSuccAt)) / 3_600_000 : null;
3933
+ if (lastSuccAt) {
3934
+ const stallTag = hoursSinceSuccess !== null && hoursSinceSuccess > 36 ? ` ${YELLOW}⚠ stalled${RESET}` : ` ${GREEN}✓${RESET}`;
3935
+ console.log(` ${DIM}Last successful: ${RESET}${lastSuccAt} ${DIM}(${formatAge(lastSuccAt)})${RESET}${stallTag}`);
3936
+ }
3937
+ else {
3938
+ console.log(` ${DIM}Last successful: ${RESET}never ${YELLOW}⚠ no cycles yet${RESET}`);
3939
+ }
3940
+ console.log(` ${DIM}Total experiments:${RESET} ${state.totalExperiments}`);
3941
+ console.log(` ${DIM}Auto-applied (7d):${RESET} ${autoAppliedRecent}`);
3942
+ console.log(` ${DIM}Pending review: ${RESET}${pending.length > 0 ? `${YELLOW}${pending.length}${RESET}` : '0'}`);
3943
+ if (state.infraError) {
3944
+ console.log();
3945
+ console.log(` ${RED}⚠ Infra error blocking the loop:${RESET}`);
3946
+ console.log(` Category: ${state.infraError.category}`);
3947
+ console.log(` Diagnostic: ${state.infraError.diagnostic.slice(0, 200)}`);
3948
+ }
3949
+ else {
3950
+ console.log(` ${DIM}Infra errors: ${RESET}${GREEN}none${RESET}`);
3951
+ }
3952
+ // ── Per-agent cycles ───────────────────────────────────────────
3953
+ const agentEntries = Object.entries(perAgentRuns);
3954
+ if (agentEntries.length > 0) {
3955
+ console.log();
3956
+ console.log(` ${BOLD}Per-agent cycles${RESET} ${DIM}(weekly cadence, 2 AM)${RESET}`);
3957
+ for (const [slug, iso] of agentEntries) {
3958
+ console.log(` ${CYAN}${slug.padEnd(28)}${RESET}${DIM}last run ${formatAge(iso)}${RESET}`);
3959
+ }
3960
+ }
3961
+ // ── Recent activity ────────────────────────────────────────────
3962
+ const recent = log.slice(-5).reverse();
3963
+ if (recent.length > 0) {
3964
+ console.log();
3965
+ console.log(` ${BOLD}Recent activity${RESET}`);
3966
+ for (const e of recent) {
3967
+ const score = (e.score * 10).toFixed(1);
3968
+ let icon = '❌';
3969
+ if (e.approvalStatus === 'approved')
3970
+ icon = '✅';
3971
+ else if (e.approvalStatus === 'pending')
3972
+ icon = '⏳';
3973
+ else if (e.approvalStatus === 'unsurfaced')
3974
+ icon = '⛔';
3975
+ const what = e.hypothesis.slice(0, 60);
3976
+ console.log(` ${icon} ${DIM}#${String(e.iteration).padEnd(3)}${RESET} ${e.area.padEnd(16)} ${score.padStart(4)}/10 "${what}"`);
3977
+ }
3978
+ }
3979
+ if (pending.length > 0) {
3980
+ console.log();
3981
+ console.log(` ${YELLOW}${pending.length} change(s) pending your review${RESET}`);
3982
+ console.log(` ${BOLD}clementine self-improve pending${RESET} ${DIM}— see what they propose${RESET}`);
3983
+ console.log(` ${BOLD}clementine self-improve apply <id>${RESET} ${DIM}— approve and apply one${RESET}`);
3984
+ }
3985
+ console.log();
3986
+ }
3987
+ catch (err) {
3988
+ console.error('Error:', err);
3989
+ process.exit(1);
3990
+ }
3991
+ });
3992
+ siCmd
3993
+ .command('pending')
3994
+ .description('List pending self-improve changes — what needs your review')
3995
+ .option('--json', 'Emit machine-readable JSON')
3996
+ .action(async (opts) => {
3997
+ const BOLD = '\x1b[1m';
3998
+ const DIM = '\x1b[0;90m';
3999
+ const YELLOW = '\x1b[1;33m';
4000
+ const CYAN = '\x1b[0;36m';
4001
+ const RESET = '\x1b[0m';
4002
+ try {
4003
+ process.env.CLEMENTINE_HOME = BASE_DIR;
4004
+ const { SelfImproveLoop } = await import('../agent/self-improve.js');
4005
+ const { PersonalAssistant } = await import('../agent/assistant.js');
4006
+ const assistant = new PersonalAssistant();
4007
+ const loop = new SelfImproveLoop(assistant);
4008
+ const pending = loop.getPendingChanges();
4009
+ if (opts.json) {
4010
+ console.log(JSON.stringify(pending.map(p => ({
4011
+ id: p.id, area: p.area, target: p.target,
4012
+ score: p.score, hypothesis: p.hypothesis, reason: p.reason,
4013
+ })), null, 2));
4014
+ return;
4015
+ }
4016
+ if (pending.length === 0) {
4017
+ console.log();
4018
+ console.log(` ${DIM}No changes pending review.${RESET}`);
4019
+ console.log();
4020
+ return;
4021
+ }
4022
+ console.log();
4023
+ console.log(` ${YELLOW}${pending.length} change${pending.length === 1 ? '' : 's'} pending${RESET}`);
4024
+ console.log();
4025
+ for (const p of pending) {
4026
+ const score = (p.score * 10).toFixed(1);
4027
+ console.log(` ${BOLD}#${p.id}${RESET} ${CYAN}${p.area}${RESET} ${DIM}→${RESET} ${p.target} ${DIM}(score ${score}/10)${RESET}`);
4028
+ console.log(` ${p.hypothesis}`);
4029
+ console.log(` ${DIM}${p.reason}${RESET}`);
4030
+ console.log();
4031
+ }
4032
+ console.log(` Apply: ${BOLD}clementine self-improve apply <id>${RESET}`);
4033
+ console.log();
3879
4034
  }
3880
4035
  catch (err) {
3881
4036
  console.error('Error:', err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.1.28",
3
+ "version": "1.1.29",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",