clementine-agent 1.1.27 → 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.
- package/dist/agent/brain-digest.d.ts +8 -1
- package/dist/agent/brain-digest.js +74 -5
- package/dist/agent/self-improve.js +30 -9
- package/dist/cli/index.js +163 -8
- package/package.json +1 -1
|
@@ -47,7 +47,14 @@ export declare function gatherBrainDigestInputs(opts: {
|
|
|
47
47
|
}): BrainDigestInputs;
|
|
48
48
|
/**
|
|
49
49
|
* Format the raw inputs as a single text block the LLM can synthesize.
|
|
50
|
-
*
|
|
50
|
+
* Pre-LLM compression: rank by signal-bearing fields (failures over runs,
|
|
51
|
+
* agent-spread over cluster size, growth over alpha-sort) and summarize the
|
|
52
|
+
* tail rather than dropping it. Same picture in fewer tokens — the model
|
|
53
|
+
* still sees the long-tail counts but doesn't pay tokens for each entry.
|
|
54
|
+
*
|
|
55
|
+
* Inspired by the skill-chaining COMPRESS pattern: filter at the boundary,
|
|
56
|
+
* synthesize in the LLM. Tail-summary lines preserve the volume signal
|
|
57
|
+
* ("X more agents added Y chunks total") without per-row cost.
|
|
51
58
|
*/
|
|
52
59
|
export declare function formatRawMaterial(inputs: BrainDigestInputs): string;
|
|
53
60
|
export declare function runBrainDigest(opts: {
|
|
@@ -114,38 +114,107 @@ function gatherMemoryDeltas(memoryStore, sinceIso) {
|
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
116
|
* Format the raw inputs as a single text block the LLM can synthesize.
|
|
117
|
-
*
|
|
117
|
+
* Pre-LLM compression: rank by signal-bearing fields (failures over runs,
|
|
118
|
+
* agent-spread over cluster size, growth over alpha-sort) and summarize the
|
|
119
|
+
* tail rather than dropping it. Same picture in fewer tokens — the model
|
|
120
|
+
* still sees the long-tail counts but doesn't pay tokens for each entry.
|
|
121
|
+
*
|
|
122
|
+
* Inspired by the skill-chaining COMPRESS pattern: filter at the boundary,
|
|
123
|
+
* synthesize in the LLM. Tail-summary lines preserve the volume signal
|
|
124
|
+
* ("X more agents added Y chunks total") without per-row cost.
|
|
118
125
|
*/
|
|
119
126
|
export function formatRawMaterial(inputs) {
|
|
120
127
|
const sections = [];
|
|
121
128
|
sections.push(`## Window\nLast ${inputs.windowDays} days.`);
|
|
122
|
-
|
|
129
|
+
// Team roster — split active vs. quiet so the synthesis prompt naturally
|
|
130
|
+
// weights active agents in "per-agent highlights" without confabulating
|
|
131
|
+
// about agents that did nothing this window.
|
|
132
|
+
const activeSlugSet = new Set([
|
|
133
|
+
...inputs.cronRunsByJob.map(r => r.agentSlug).filter((s) => !!s),
|
|
134
|
+
...inputs.memoryDeltas.filter(d => d.agentSlug !== 'global').map(d => d.agentSlug),
|
|
135
|
+
]);
|
|
136
|
+
const activeAgents = inputs.agents.filter(a => activeSlugSet.has(a.slug));
|
|
137
|
+
const quietAgents = inputs.agents.filter(a => !activeSlugSet.has(a.slug));
|
|
138
|
+
if (inputs.agents.length === 0) {
|
|
139
|
+
sections.push(`## Team roster\n(no specialist agents)`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const lines = [];
|
|
143
|
+
if (activeAgents.length > 0) {
|
|
144
|
+
lines.push(`Active this window:\n${activeAgents.map(a => `- ${a.name} (${a.slug})`).join('\n')}`);
|
|
145
|
+
}
|
|
146
|
+
if (quietAgents.length > 0) {
|
|
147
|
+
lines.push(`Quiet this window: ${quietAgents.map(a => a.slug).join(', ')}`);
|
|
148
|
+
}
|
|
149
|
+
sections.push(`## Team roster\n${lines.join('\n\n')}`);
|
|
150
|
+
}
|
|
151
|
+
// Cron activity — failures-first ranking so the synthesis prompt sees the
|
|
152
|
+
// problem signal early, with a tail summary preserving total volume.
|
|
123
153
|
if (inputs.cronRunsByJob.length === 0) {
|
|
124
154
|
sections.push(`## Cron activity\n(no autonomous runs in window)`);
|
|
125
155
|
}
|
|
126
156
|
else {
|
|
127
|
-
const
|
|
157
|
+
const ranked = [...inputs.cronRunsByJob].sort((a, b) => {
|
|
158
|
+
// Failures dominate; ties broken by run count (busier jobs more important).
|
|
159
|
+
if (b.failures !== a.failures)
|
|
160
|
+
return b.failures - a.failures;
|
|
161
|
+
return b.runs - a.runs;
|
|
162
|
+
});
|
|
163
|
+
const TOP_N = 12;
|
|
164
|
+
const top = ranked.slice(0, TOP_N);
|
|
165
|
+
const tail = ranked.slice(TOP_N);
|
|
166
|
+
const lines = top.map(r => {
|
|
128
167
|
const tag = r.agentSlug ? ` [${r.agentSlug}]` : '';
|
|
129
168
|
const failTag = r.failures > 0 ? ` — ${r.failures} failure${r.failures === 1 ? '' : 's'}` : '';
|
|
130
169
|
return `- ${r.jobName}${tag}: ${r.runs} run${r.runs === 1 ? '' : 's'}${failTag}`;
|
|
131
170
|
});
|
|
171
|
+
if (tail.length > 0) {
|
|
172
|
+
const tailRuns = tail.reduce((s, r) => s + r.runs, 0);
|
|
173
|
+
const tailFailures = tail.reduce((s, r) => s + r.failures, 0);
|
|
174
|
+
lines.push(`- _…and ${tail.length} more job${tail.length === 1 ? '' : 's'}: ${tailRuns} runs, ${tailFailures} failures total_`);
|
|
175
|
+
}
|
|
132
176
|
sections.push(`## Cron activity\n${lines.join('\n')}`);
|
|
133
177
|
}
|
|
178
|
+
// Memory growth — top-N by delta, summarize rest. The LLM doesn't need a
|
|
179
|
+
// 30-line list of every agent that wrote one chunk; it needs to know who
|
|
180
|
+
// wrote a lot.
|
|
134
181
|
if (inputs.memoryDeltas.length === 0) {
|
|
135
182
|
sections.push(`## Memory growth\n(no new chunks in window)`);
|
|
136
183
|
}
|
|
137
184
|
else {
|
|
138
|
-
const
|
|
185
|
+
const TOP_N = 8;
|
|
186
|
+
const top = inputs.memoryDeltas.slice(0, TOP_N);
|
|
187
|
+
const tail = inputs.memoryDeltas.slice(TOP_N);
|
|
188
|
+
const lines = top.map(d => `- ${d.agentSlug}: +${d.chunksAdded} chunks`);
|
|
189
|
+
if (tail.length > 0) {
|
|
190
|
+
const tailTotal = tail.reduce((s, d) => s + d.chunksAdded, 0);
|
|
191
|
+
lines.push(`- _…and ${tail.length} other agent${tail.length === 1 ? '' : 's'}: +${tailTotal} chunks combined_`);
|
|
192
|
+
}
|
|
139
193
|
sections.push(`## Memory growth\n${lines.join('\n')}`);
|
|
140
194
|
}
|
|
195
|
+
// Cross-agent recurrence — widest-spread clusters first (most agents
|
|
196
|
+
// touched), with cluster size as tiebreaker. Spread is the signal of
|
|
197
|
+
// "this is genuinely team knowledge" — a 3-cluster touching 4 agents
|
|
198
|
+
// matters more than a 10-cluster touching 2.
|
|
141
199
|
if (inputs.crossAgentClusters.length === 0) {
|
|
142
200
|
sections.push(`## Cross-agent recurrence\n(no facts surfaced from 2+ agents)`);
|
|
143
201
|
}
|
|
144
202
|
else {
|
|
145
|
-
const
|
|
203
|
+
const ranked = [...inputs.crossAgentClusters].sort((a, b) => {
|
|
204
|
+
if (b.agents.length !== a.agents.length)
|
|
205
|
+
return b.agents.length - a.agents.length;
|
|
206
|
+
return b.memberCount - a.memberCount;
|
|
207
|
+
});
|
|
208
|
+
const TOP_N = 8;
|
|
209
|
+
const top = ranked.slice(0, TOP_N);
|
|
210
|
+
const tail = ranked.slice(TOP_N);
|
|
211
|
+
const lines = top.map((c, i) => {
|
|
146
212
|
const preview = c.representativeContent.replace(/\n/g, ' ').slice(0, 200);
|
|
147
213
|
return `${i + 1}. agents: ${c.agents.join(', ')} (${c.memberCount} chunks)\n "${preview}${preview.length >= 200 ? '…' : ''}"`;
|
|
148
214
|
});
|
|
215
|
+
if (tail.length > 0) {
|
|
216
|
+
lines.push(`_…and ${tail.length} more cross-agent cluster${tail.length === 1 ? '' : 's'} (smaller spread, not surfaced individually)._`);
|
|
217
|
+
}
|
|
149
218
|
sections.push(`## Cross-agent recurrence\n${lines.join('\n')}`);
|
|
150
219
|
}
|
|
151
220
|
return sections.join('\n\n');
|
|
@@ -768,19 +768,40 @@ export class SelfImproveLoop {
|
|
|
768
768
|
`Choose a DIFFERENT area/target. If no other improvement is genuinely needed today, return an empty results array: { "results": [] }.\n`
|
|
769
769
|
: '');
|
|
770
770
|
const patternAnalysis = this.analyzeExperimentPatterns(history);
|
|
771
|
-
//
|
|
772
|
-
|
|
773
|
-
//
|
|
774
|
-
|
|
775
|
-
//
|
|
776
|
-
|
|
771
|
+
// Pre-LLM compression: when this is a focused per-agent cycle, filter
|
|
772
|
+
// every metrics text to that agent's own data. Without this, the LLM
|
|
773
|
+
// sees ALL agents' cron errors / reflections and may propose changes
|
|
774
|
+
// for the focused agent based on signals from a totally different one.
|
|
775
|
+
// (Skill-chaining COMPRESS pattern: filter at the boundary, synthesize
|
|
776
|
+
// in the LLM.)
|
|
777
|
+
const focusedSlug = this.config.agentSlug;
|
|
778
|
+
const isAgentScoped = (jobName) => !!focusedSlug && jobName.startsWith(`${focusedSlug}:`);
|
|
779
|
+
const filteredNegativeFeedback = focusedSlug
|
|
780
|
+
// Negative feedback rows don't carry agent tags reliably — keep all
|
|
781
|
+
// when in agent mode but cap tighter so noise stays bounded.
|
|
782
|
+
? metrics.negativeFeedback.slice(0, 3)
|
|
783
|
+
: metrics.negativeFeedback.slice(0, 5);
|
|
784
|
+
const negativeFeedbackText = filteredNegativeFeedback.map(f => `- Rating: ${f.rating} | Message: "${(f.messageSnippet ?? '').slice(0, 100)}" | Response: "${(f.responseSnippet ?? '').slice(0, 100)}"${f.comment ? ` | Comment: "${f.comment}"` : ''}`).join('\n') || '(no negative feedback)';
|
|
785
|
+
// Format cron errors — filter to focused agent's jobs when applicable.
|
|
786
|
+
const filteredCronErrors = focusedSlug
|
|
787
|
+
? metrics.cronErrors.filter(e => isAgentScoped(e.jobName ?? ''))
|
|
788
|
+
: metrics.cronErrors;
|
|
789
|
+
const cronErrorsText = filteredCronErrors.slice(0, 5).map(e => `- Job: ${e.jobName} | Error: ${(e.error ?? 'unknown').slice(0, 200)} | At: ${e.startedAt}`).join('\n') || (focusedSlug ? `(no cron errors for ${focusedSlug} in window)` : '(no cron errors)');
|
|
790
|
+
// Format cron reflections — same filter.
|
|
791
|
+
const filteredReflections = focusedSlug
|
|
792
|
+
? metrics.cronReflections.filter(r => r.agentSlug === focusedSlug || isAgentScoped(r.jobName ?? ''))
|
|
793
|
+
: metrics.cronReflections;
|
|
794
|
+
const cronReflectionsText = filteredReflections.slice(-10).map(r => `- Job: ${r.jobName}${r.agentSlug ? ` (${r.agentSlug})` : ''} | Quality: ${r.quality}/5 | ` +
|
|
777
795
|
`Exist: ${r.existence ?? '?'} Substance: ${r.substance ?? '?'} Actionable: ${r.actionable ?? '?'} ` +
|
|
778
796
|
`Comm: ${r.communication ?? '?'} | ` +
|
|
779
|
-
`Gap: "${r.gap?.slice(0, 80) ?? ''}"${r.commNote ? ` | CommNote: "${r.commNote.slice(0, 80)}"` : ''} | At: ${r.timestamp}`).join('\n') || '(no cron reflections yet)';
|
|
780
|
-
// Compute per-agent metrics from reflections
|
|
797
|
+
`Gap: "${r.gap?.slice(0, 80) ?? ''}"${r.commNote ? ` | CommNote: "${r.commNote.slice(0, 80)}"` : ''} | At: ${r.timestamp}`).join('\n') || (focusedSlug ? `(no cron reflections for ${focusedSlug} yet)` : '(no cron reflections yet)');
|
|
798
|
+
// Compute per-agent metrics from reflections — when focused, only show
|
|
799
|
+
// this agent's row (the others are irrelevant to the proposal).
|
|
781
800
|
const agentMetrics = new Map();
|
|
782
801
|
for (const r of metrics.cronReflections) {
|
|
783
802
|
const slug = r.agentSlug || 'clementine';
|
|
803
|
+
if (focusedSlug && slug !== focusedSlug)
|
|
804
|
+
continue;
|
|
784
805
|
if (!agentMetrics.has(slug)) {
|
|
785
806
|
agentMetrics.set(slug, { total: 0, qualitySum: 0, emptyCount: 0, gaps: [] });
|
|
786
807
|
}
|
|
@@ -799,7 +820,7 @@ export class SelfImproveLoop {
|
|
|
799
820
|
const topGaps = m.gaps.slice(-3).map(g => g.slice(0, 60)).join('; ') || 'none';
|
|
800
821
|
return `- ${slug}: avg quality ${avgQ}/5, ${emptyPct}% empty outputs, common gaps: "${topGaps}"`;
|
|
801
822
|
}).join('\n')
|
|
802
|
-
: '(no per-agent data yet)';
|
|
823
|
+
: (focusedSlug ? `(no reflection data for ${focusedSlug} yet)` : '(no per-agent data yet)');
|
|
803
824
|
// Format goal health data
|
|
804
825
|
const goalHealthText = metrics.goalHealth.length > 0
|
|
805
826
|
? metrics.goalHealth.map(g => {
|
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
|
|
3866
|
-
.
|
|
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
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
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);
|