clementine-agent 1.0.0
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/.env.example +44 -0
- package/LICENSE +21 -0
- package/README.md +795 -0
- package/dist/agent/agent-manager.d.ts +69 -0
- package/dist/agent/agent-manager.js +441 -0
- package/dist/agent/assistant.d.ts +225 -0
- package/dist/agent/assistant.js +3888 -0
- package/dist/agent/auto-update.d.ts +32 -0
- package/dist/agent/auto-update.js +186 -0
- package/dist/agent/daily-planner.d.ts +24 -0
- package/dist/agent/daily-planner.js +379 -0
- package/dist/agent/execution-advisor.d.ts +10 -0
- package/dist/agent/execution-advisor.js +272 -0
- package/dist/agent/hooks.d.ts +45 -0
- package/dist/agent/hooks.js +564 -0
- package/dist/agent/insight-engine.d.ts +66 -0
- package/dist/agent/insight-engine.js +225 -0
- package/dist/agent/intent-classifier.d.ts +48 -0
- package/dist/agent/intent-classifier.js +214 -0
- package/dist/agent/link-extractor.d.ts +19 -0
- package/dist/agent/link-extractor.js +90 -0
- package/dist/agent/mcp-bridge.d.ts +62 -0
- package/dist/agent/mcp-bridge.js +435 -0
- package/dist/agent/metacognition.d.ts +66 -0
- package/dist/agent/metacognition.js +221 -0
- package/dist/agent/orchestrator.d.ts +81 -0
- package/dist/agent/orchestrator.js +790 -0
- package/dist/agent/profiles.d.ts +22 -0
- package/dist/agent/profiles.js +91 -0
- package/dist/agent/prompt-cache.d.ts +24 -0
- package/dist/agent/prompt-cache.js +68 -0
- package/dist/agent/prompt-evolver.d.ts +28 -0
- package/dist/agent/prompt-evolver.js +279 -0
- package/dist/agent/role-scaffolds.d.ts +28 -0
- package/dist/agent/role-scaffolds.js +433 -0
- package/dist/agent/safe-restart.d.ts +41 -0
- package/dist/agent/safe-restart.js +150 -0
- package/dist/agent/self-improve.d.ts +66 -0
- package/dist/agent/self-improve.js +1706 -0
- package/dist/agent/session-event-log.d.ts +114 -0
- package/dist/agent/session-event-log.js +233 -0
- package/dist/agent/skill-extractor.d.ts +72 -0
- package/dist/agent/skill-extractor.js +435 -0
- package/dist/agent/source-mods.d.ts +61 -0
- package/dist/agent/source-mods.js +230 -0
- package/dist/agent/source-preflight.d.ts +25 -0
- package/dist/agent/source-preflight.js +100 -0
- package/dist/agent/stall-guard.d.ts +62 -0
- package/dist/agent/stall-guard.js +109 -0
- package/dist/agent/strategic-planner.d.ts +60 -0
- package/dist/agent/strategic-planner.js +352 -0
- package/dist/agent/team-bus.d.ts +89 -0
- package/dist/agent/team-bus.js +556 -0
- package/dist/agent/team-router.d.ts +26 -0
- package/dist/agent/team-router.js +37 -0
- package/dist/agent/tool-loop-detector.d.ts +59 -0
- package/dist/agent/tool-loop-detector.js +242 -0
- package/dist/agent/workflow-runner.d.ts +36 -0
- package/dist/agent/workflow-runner.js +317 -0
- package/dist/agent/workflow-variables.d.ts +16 -0
- package/dist/agent/workflow-variables.js +62 -0
- package/dist/channels/discord-agent-bot.d.ts +101 -0
- package/dist/channels/discord-agent-bot.js +881 -0
- package/dist/channels/discord-bot-manager.d.ts +80 -0
- package/dist/channels/discord-bot-manager.js +262 -0
- package/dist/channels/discord-utils.d.ts +51 -0
- package/dist/channels/discord-utils.js +293 -0
- package/dist/channels/discord.d.ts +12 -0
- package/dist/channels/discord.js +1832 -0
- package/dist/channels/slack-agent-bot.d.ts +73 -0
- package/dist/channels/slack-agent-bot.js +320 -0
- package/dist/channels/slack-bot-manager.d.ts +66 -0
- package/dist/channels/slack-bot-manager.js +236 -0
- package/dist/channels/slack-utils.d.ts +39 -0
- package/dist/channels/slack-utils.js +189 -0
- package/dist/channels/slack.d.ts +11 -0
- package/dist/channels/slack.js +196 -0
- package/dist/channels/telegram.d.ts +10 -0
- package/dist/channels/telegram.js +235 -0
- package/dist/channels/webhook.d.ts +9 -0
- package/dist/channels/webhook.js +78 -0
- package/dist/channels/whatsapp.d.ts +11 -0
- package/dist/channels/whatsapp.js +181 -0
- package/dist/cli/chat.d.ts +14 -0
- package/dist/cli/chat.js +220 -0
- package/dist/cli/cron.d.ts +17 -0
- package/dist/cli/cron.js +552 -0
- package/dist/cli/dashboard.d.ts +15 -0
- package/dist/cli/dashboard.js +17677 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +2474 -0
- package/dist/cli/routes/delegations.d.ts +19 -0
- package/dist/cli/routes/delegations.js +154 -0
- package/dist/cli/routes/digest.d.ts +17 -0
- package/dist/cli/routes/digest.js +375 -0
- package/dist/cli/routes/goals.d.ts +14 -0
- package/dist/cli/routes/goals.js +258 -0
- package/dist/cli/routes/workflows.d.ts +18 -0
- package/dist/cli/routes/workflows.js +97 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +619 -0
- package/dist/cli/tunnel.d.ts +35 -0
- package/dist/cli/tunnel.js +141 -0
- package/dist/config.d.ts +145 -0
- package/dist/config.js +278 -0
- package/dist/events/bus.d.ts +43 -0
- package/dist/events/bus.js +136 -0
- package/dist/gateway/cron-scheduler.d.ts +166 -0
- package/dist/gateway/cron-scheduler.js +1767 -0
- package/dist/gateway/delivery-queue.d.ts +30 -0
- package/dist/gateway/delivery-queue.js +110 -0
- package/dist/gateway/heartbeat-scheduler.d.ts +99 -0
- package/dist/gateway/heartbeat-scheduler.js +1298 -0
- package/dist/gateway/heartbeat.d.ts +3 -0
- package/dist/gateway/heartbeat.js +3 -0
- package/dist/gateway/lanes.d.ts +24 -0
- package/dist/gateway/lanes.js +76 -0
- package/dist/gateway/notifications.d.ts +29 -0
- package/dist/gateway/notifications.js +75 -0
- package/dist/gateway/router.d.ts +210 -0
- package/dist/gateway/router.js +1330 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1015 -0
- package/dist/memory/chunker.d.ts +28 -0
- package/dist/memory/chunker.js +226 -0
- package/dist/memory/consolidation.d.ts +44 -0
- package/dist/memory/consolidation.js +171 -0
- package/dist/memory/context-assembler.d.ts +50 -0
- package/dist/memory/context-assembler.js +149 -0
- package/dist/memory/embeddings.d.ts +38 -0
- package/dist/memory/embeddings.js +180 -0
- package/dist/memory/graph-store.d.ts +66 -0
- package/dist/memory/graph-store.js +613 -0
- package/dist/memory/mmr.d.ts +21 -0
- package/dist/memory/mmr.js +75 -0
- package/dist/memory/search.d.ts +26 -0
- package/dist/memory/search.js +67 -0
- package/dist/memory/store.d.ts +530 -0
- package/dist/memory/store.js +2022 -0
- package/dist/security/integrity.d.ts +24 -0
- package/dist/security/integrity.js +58 -0
- package/dist/security/patterns.d.ts +34 -0
- package/dist/security/patterns.js +110 -0
- package/dist/security/scanner.d.ts +32 -0
- package/dist/security/scanner.js +263 -0
- package/dist/tools/admin-tools.d.ts +12 -0
- package/dist/tools/admin-tools.js +1278 -0
- package/dist/tools/external-tools.d.ts +11 -0
- package/dist/tools/external-tools.js +1327 -0
- package/dist/tools/goal-tools.d.ts +9 -0
- package/dist/tools/goal-tools.js +159 -0
- package/dist/tools/mcp-server.d.ts +13 -0
- package/dist/tools/mcp-server.js +141 -0
- package/dist/tools/memory-tools.d.ts +10 -0
- package/dist/tools/memory-tools.js +568 -0
- package/dist/tools/session-tools.d.ts +6 -0
- package/dist/tools/session-tools.js +146 -0
- package/dist/tools/shared.d.ts +216 -0
- package/dist/tools/shared.js +340 -0
- package/dist/tools/team-tools.d.ts +6 -0
- package/dist/tools/team-tools.js +447 -0
- package/dist/tools/tool-meta.d.ts +34 -0
- package/dist/tools/tool-meta.js +133 -0
- package/dist/tools/vault-tools.d.ts +8 -0
- package/dist/tools/vault-tools.js +457 -0
- package/dist/types.d.ts +716 -0
- package/dist/types.js +16 -0
- package/dist/vault-migrations/0001-add-execution-framework.d.ts +10 -0
- package/dist/vault-migrations/0001-add-execution-framework.js +47 -0
- package/dist/vault-migrations/0002-add-agentic-communication.d.ts +12 -0
- package/dist/vault-migrations/0002-add-agentic-communication.js +79 -0
- package/dist/vault-migrations/0003-update-execution-pipeline-narration.d.ts +11 -0
- package/dist/vault-migrations/0003-update-execution-pipeline-narration.js +73 -0
- package/dist/vault-migrations/helpers.d.ts +14 -0
- package/dist/vault-migrations/helpers.js +44 -0
- package/dist/vault-migrations/runner.d.ts +14 -0
- package/dist/vault-migrations/runner.js +139 -0
- package/dist/vault-migrations/types.d.ts +42 -0
- package/dist/vault-migrations/types.js +9 -0
- package/install.sh +320 -0
- package/package.json +84 -0
- package/scripts/postinstall.js +125 -0
- package/vault/00-System/AGENTS.md +66 -0
- package/vault/00-System/CRON.md +71 -0
- package/vault/00-System/HEARTBEAT.md +58 -0
- package/vault/00-System/MEMORY.md +16 -0
- package/vault/00-System/SOUL.md +96 -0
- package/vault/05-Tasks/TASKS.md +19 -0
- package/vault/06-Templates/_Daily-Template.md +28 -0
- package/vault/06-Templates/_People-Template.md +22 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Source Modification Registry.
|
|
3
|
+
*
|
|
4
|
+
* Tracks self-improve source edits in ~/.clementine/ (not in git).
|
|
5
|
+
* When `clementine update` pulls new code, the reconciliation step
|
|
6
|
+
* re-applies active modifications that are still needed.
|
|
7
|
+
*
|
|
8
|
+
* This decouples user-local improvements from the upstream repo,
|
|
9
|
+
* so `git pull` is always clean and user customizations survive.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, rmSync, } from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import pino from 'pino';
|
|
15
|
+
import { SOURCE_MODS_DIR } from '../config.js';
|
|
16
|
+
const logger = pino({ name: 'clementine.source-mods' });
|
|
17
|
+
// ── Registry Operations ──────────────────────────────────────────────
|
|
18
|
+
function ensureDir() {
|
|
19
|
+
if (!existsSync(SOURCE_MODS_DIR)) {
|
|
20
|
+
mkdirSync(SOURCE_MODS_DIR, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Record a new source modification with before/after file snapshots. */
|
|
24
|
+
export function recordSourceMod(id, files, opts) {
|
|
25
|
+
ensureDir();
|
|
26
|
+
const record = {
|
|
27
|
+
id,
|
|
28
|
+
files: files.map(f => f.relativePath),
|
|
29
|
+
reason: opts.reason,
|
|
30
|
+
description: opts.description,
|
|
31
|
+
experimentId: opts.experimentId,
|
|
32
|
+
appliedAt: new Date().toISOString(),
|
|
33
|
+
status: 'active',
|
|
34
|
+
};
|
|
35
|
+
// Write metadata
|
|
36
|
+
writeFileSync(path.join(SOURCE_MODS_DIR, `${id}.json`), JSON.stringify(record, null, 2));
|
|
37
|
+
// Write before/after snapshots
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const beforeDir = path.join(SOURCE_MODS_DIR, `${id}.before`);
|
|
40
|
+
const afterDir = path.join(SOURCE_MODS_DIR, `${id}.after`);
|
|
41
|
+
mkdirSync(path.join(beforeDir, path.dirname(file.relativePath)), { recursive: true });
|
|
42
|
+
mkdirSync(path.join(afterDir, path.dirname(file.relativePath)), { recursive: true });
|
|
43
|
+
writeFileSync(path.join(beforeDir, file.relativePath), file.beforeContent);
|
|
44
|
+
writeFileSync(path.join(afterDir, file.relativePath), file.afterContent);
|
|
45
|
+
}
|
|
46
|
+
logger.info({ id, files: record.files, reason: opts.reason }, 'Source modification recorded');
|
|
47
|
+
}
|
|
48
|
+
/** Load all source mod records. */
|
|
49
|
+
export function loadSourceMods() {
|
|
50
|
+
ensureDir();
|
|
51
|
+
return readdirSync(SOURCE_MODS_DIR)
|
|
52
|
+
.filter(f => f.endsWith('.json'))
|
|
53
|
+
.map(f => {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(readFileSync(path.join(SOURCE_MODS_DIR, f), 'utf-8'));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
/** Load only active mods. */
|
|
64
|
+
export function loadActiveSourceMods() {
|
|
65
|
+
return loadSourceMods().filter(m => m.status === 'active');
|
|
66
|
+
}
|
|
67
|
+
/** Update a mod's status. */
|
|
68
|
+
export function updateModStatus(id, status) {
|
|
69
|
+
const filePath = path.join(SOURCE_MODS_DIR, `${id}.json`);
|
|
70
|
+
if (!existsSync(filePath))
|
|
71
|
+
return;
|
|
72
|
+
const record = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
73
|
+
record.status = status;
|
|
74
|
+
writeFileSync(filePath, JSON.stringify(record, null, 2));
|
|
75
|
+
}
|
|
76
|
+
/** Remove a mod and its snapshots entirely. */
|
|
77
|
+
export function removeSourceMod(id) {
|
|
78
|
+
const jsonPath = path.join(SOURCE_MODS_DIR, `${id}.json`);
|
|
79
|
+
const beforeDir = path.join(SOURCE_MODS_DIR, `${id}.before`);
|
|
80
|
+
const afterDir = path.join(SOURCE_MODS_DIR, `${id}.after`);
|
|
81
|
+
try {
|
|
82
|
+
if (existsSync(jsonPath))
|
|
83
|
+
rmSync(jsonPath);
|
|
84
|
+
}
|
|
85
|
+
catch { /* best effort */ }
|
|
86
|
+
try {
|
|
87
|
+
if (existsSync(beforeDir))
|
|
88
|
+
rmSync(beforeDir, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
catch { /* best effort */ }
|
|
91
|
+
try {
|
|
92
|
+
if (existsSync(afterDir))
|
|
93
|
+
rmSync(afterDir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
catch { /* best effort */ }
|
|
96
|
+
}
|
|
97
|
+
/** Read the stored "after" content for a mod's file. */
|
|
98
|
+
export function readModAfterContent(id, relativePath) {
|
|
99
|
+
const filePath = path.join(SOURCE_MODS_DIR, `${id}.after`, relativePath);
|
|
100
|
+
if (!existsSync(filePath))
|
|
101
|
+
return null;
|
|
102
|
+
return readFileSync(filePath, 'utf-8');
|
|
103
|
+
}
|
|
104
|
+
/** Read the stored "before" content for a mod's file (for rollback). */
|
|
105
|
+
export function readModBeforeContent(id, relativePath) {
|
|
106
|
+
const filePath = path.join(SOURCE_MODS_DIR, `${id}.before`, relativePath);
|
|
107
|
+
if (!existsSync(filePath))
|
|
108
|
+
return null;
|
|
109
|
+
return readFileSync(filePath, 'utf-8');
|
|
110
|
+
}
|
|
111
|
+
// ── Rollback ─────────────────────────────────────────────────────────
|
|
112
|
+
/** Rollback a source mod by restoring the "before" snapshots. */
|
|
113
|
+
export function rollbackSourceMod(id, pkgDir) {
|
|
114
|
+
const record = loadSourceMods().find(m => m.id === id);
|
|
115
|
+
if (!record)
|
|
116
|
+
return false;
|
|
117
|
+
for (const relativePath of record.files) {
|
|
118
|
+
const before = readModBeforeContent(id, relativePath);
|
|
119
|
+
if (before !== null) {
|
|
120
|
+
writeFileSync(path.join(pkgDir, relativePath), before);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
updateModStatus(id, 'failed');
|
|
124
|
+
logger.info({ id }, 'Source modification rolled back');
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
// ── Reconciliation (post-update) ─────────────────────────────────────
|
|
128
|
+
function fileHash(content) {
|
|
129
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Reconcile active source mods after an upstream update.
|
|
133
|
+
*
|
|
134
|
+
* For each active mod:
|
|
135
|
+
* 1. If the current file already matches our "after" content → superseded
|
|
136
|
+
* 2. If the current file matches our "before" content → re-apply directly
|
|
137
|
+
* 3. If the current file is different from both → needs LLM reconciliation
|
|
138
|
+
*
|
|
139
|
+
* After re-applying, runs a typecheck. Failures get reverted.
|
|
140
|
+
*/
|
|
141
|
+
export function reconcileSourceMods(pkgDir) {
|
|
142
|
+
const result = {
|
|
143
|
+
reapplied: [],
|
|
144
|
+
superseded: [],
|
|
145
|
+
needsReconciliation: [],
|
|
146
|
+
failed: [],
|
|
147
|
+
};
|
|
148
|
+
const activeMods = loadActiveSourceMods();
|
|
149
|
+
if (activeMods.length === 0)
|
|
150
|
+
return result;
|
|
151
|
+
logger.info({ count: activeMods.length }, 'Reconciling source modifications after update');
|
|
152
|
+
for (const mod of activeMods) {
|
|
153
|
+
let modResult = 'needs-reconciliation';
|
|
154
|
+
// Check each file in the mod
|
|
155
|
+
const fileChecks = [];
|
|
156
|
+
for (const relativePath of mod.files) {
|
|
157
|
+
const currentPath = path.join(pkgDir, relativePath);
|
|
158
|
+
const currentContent = existsSync(currentPath) ? readFileSync(currentPath, 'utf-8') : '';
|
|
159
|
+
const afterContent = readModAfterContent(mod.id, relativePath);
|
|
160
|
+
const beforeContent = readModBeforeContent(mod.id, relativePath);
|
|
161
|
+
const currentHash = fileHash(currentContent);
|
|
162
|
+
const afterHash = afterContent ? fileHash(afterContent) : '';
|
|
163
|
+
const beforeHash = beforeContent ? fileHash(beforeContent) : '';
|
|
164
|
+
if (currentHash === afterHash) {
|
|
165
|
+
// Current file already has our changes (upstream included them)
|
|
166
|
+
fileChecks.push({ relativePath, action: 'superseded' });
|
|
167
|
+
}
|
|
168
|
+
else if (currentHash === beforeHash) {
|
|
169
|
+
// File is back to pre-mod state — upstream didn't change it, safe to re-apply
|
|
170
|
+
fileChecks.push({ relativePath, action: 'reapply' });
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// File changed upstream AND our mod is gone — needs intelligent merge
|
|
174
|
+
fileChecks.push({ relativePath, action: 'needs-reconciliation' });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Determine overall mod action
|
|
178
|
+
const hasNeedsRecon = fileChecks.some(f => f.action === 'needs-reconciliation');
|
|
179
|
+
const allSuperseded = fileChecks.every(f => f.action === 'superseded');
|
|
180
|
+
if (allSuperseded) {
|
|
181
|
+
modResult = 'superseded';
|
|
182
|
+
}
|
|
183
|
+
else if (hasNeedsRecon) {
|
|
184
|
+
modResult = 'needs-reconciliation';
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
modResult = 'reapply';
|
|
188
|
+
}
|
|
189
|
+
if (modResult === 'superseded') {
|
|
190
|
+
updateModStatus(mod.id, 'superseded');
|
|
191
|
+
result.superseded.push(mod.id);
|
|
192
|
+
logger.info({ id: mod.id, reason: mod.reason }, 'Source mod superseded by upstream');
|
|
193
|
+
}
|
|
194
|
+
else if (modResult === 'reapply') {
|
|
195
|
+
// Re-apply all files
|
|
196
|
+
for (const relativePath of mod.files) {
|
|
197
|
+
const afterContent = readModAfterContent(mod.id, relativePath);
|
|
198
|
+
if (afterContent) {
|
|
199
|
+
writeFileSync(path.join(pkgDir, relativePath), afterContent);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
result.reapplied.push(mod.id);
|
|
203
|
+
logger.info({ id: mod.id, reason: mod.reason }, 'Source mod re-applied after update');
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
updateModStatus(mod.id, 'needs-reconciliation');
|
|
207
|
+
result.needsReconciliation.push(mod.id);
|
|
208
|
+
logger.info({ id: mod.id, reason: mod.reason }, 'Source mod needs LLM reconciliation');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// If we re-applied anything, typecheck
|
|
212
|
+
if (result.reapplied.length > 0) {
|
|
213
|
+
try {
|
|
214
|
+
const { execSync } = require('node:child_process');
|
|
215
|
+
execSync('./node_modules/.bin/tsc --noEmit', { cwd: pkgDir, stdio: 'pipe', timeout: 120_000 });
|
|
216
|
+
logger.info({ count: result.reapplied.length }, 'Typecheck passed after re-applying source mods');
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Typecheck failed — revert all re-applied mods
|
|
220
|
+
logger.warn('Typecheck failed after re-applying source mods — reverting');
|
|
221
|
+
for (const modId of result.reapplied) {
|
|
222
|
+
rollbackSourceMod(modId, pkgDir);
|
|
223
|
+
}
|
|
224
|
+
result.failed.push(...result.reapplied);
|
|
225
|
+
result.reapplied = [];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=source-mods.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Worktree Preflight Validator.
|
|
3
|
+
*
|
|
4
|
+
* Validates proposed source changes in an isolated git worktree before
|
|
5
|
+
* they touch the live repo. Uses worktree isolation for safe validation.
|
|
6
|
+
*/
|
|
7
|
+
export interface PreflightResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
errors?: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validate proposed source changes in an isolated git worktree.
|
|
13
|
+
*
|
|
14
|
+
* 1. Create a detached worktree from HEAD
|
|
15
|
+
* 2. Symlink node_modules from the real tree
|
|
16
|
+
* 3. Write the changed .ts files into the worktree
|
|
17
|
+
* 4. Run `./node_modules/.bin/tsc --noEmit` to type-check
|
|
18
|
+
* 5. Return success/failure with compiler errors
|
|
19
|
+
* 6. Always clean up the worktree
|
|
20
|
+
*/
|
|
21
|
+
export declare function preflightSourceChange(pkgDir: string, changes: Array<{
|
|
22
|
+
relativePath: string;
|
|
23
|
+
content: string;
|
|
24
|
+
}>): Promise<PreflightResult>;
|
|
25
|
+
//# sourceMappingURL=source-preflight.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Worktree Preflight Validator.
|
|
3
|
+
*
|
|
4
|
+
* Validates proposed source changes in an isolated git worktree before
|
|
5
|
+
* they touch the live repo. Uses worktree isolation for safe validation.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import { existsSync, mkdirSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import pino from 'pino';
|
|
11
|
+
import { STAGING_DIR } from '../config.js';
|
|
12
|
+
const logger = pino({ name: 'clementine.source-preflight' });
|
|
13
|
+
/**
|
|
14
|
+
* Validate proposed source changes in an isolated git worktree.
|
|
15
|
+
*
|
|
16
|
+
* 1. Create a detached worktree from HEAD
|
|
17
|
+
* 2. Symlink node_modules from the real tree
|
|
18
|
+
* 3. Write the changed .ts files into the worktree
|
|
19
|
+
* 4. Run `./node_modules/.bin/tsc --noEmit` to type-check
|
|
20
|
+
* 5. Return success/failure with compiler errors
|
|
21
|
+
* 6. Always clean up the worktree
|
|
22
|
+
*/
|
|
23
|
+
export async function preflightSourceChange(pkgDir, changes) {
|
|
24
|
+
const timestamp = Date.now();
|
|
25
|
+
const worktreePath = path.join(STAGING_DIR, `preflight-${timestamp}`);
|
|
26
|
+
// Ensure staging directory exists
|
|
27
|
+
if (!existsSync(STAGING_DIR)) {
|
|
28
|
+
mkdirSync(STAGING_DIR, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
// 1. Create detached worktree from HEAD
|
|
32
|
+
logger.info({ worktreePath }, 'Creating preflight worktree');
|
|
33
|
+
execSync(`git worktree add --detach "${worktreePath}" HEAD`, {
|
|
34
|
+
cwd: pkgDir,
|
|
35
|
+
stdio: 'pipe',
|
|
36
|
+
});
|
|
37
|
+
// 2. Symlink node_modules from the real tree
|
|
38
|
+
const realModules = path.join(pkgDir, 'node_modules');
|
|
39
|
+
const worktreeModules = path.join(worktreePath, 'node_modules');
|
|
40
|
+
if (existsSync(realModules)) {
|
|
41
|
+
try {
|
|
42
|
+
symlinkSync(realModules, worktreeModules, 'junction');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Fallback: copy if symlink fails (e.g., cross-device)
|
|
46
|
+
logger.warn('Symlink failed — copying node_modules');
|
|
47
|
+
execSync(`cp -r "${realModules}" "${worktreeModules}"`, { stdio: 'pipe' });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// 3. Write changed .ts files into the worktree
|
|
51
|
+
for (const change of changes) {
|
|
52
|
+
const targetFile = path.join(worktreePath, change.relativePath);
|
|
53
|
+
const targetDir = path.dirname(targetFile);
|
|
54
|
+
if (!existsSync(targetDir)) {
|
|
55
|
+
mkdirSync(targetDir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
writeFileSync(targetFile, change.content);
|
|
58
|
+
}
|
|
59
|
+
// 4. Run tsc --noEmit to type-check
|
|
60
|
+
try {
|
|
61
|
+
execSync('./node_modules/.bin/tsc --noEmit', {
|
|
62
|
+
cwd: worktreePath,
|
|
63
|
+
stdio: 'pipe',
|
|
64
|
+
timeout: 60_000,
|
|
65
|
+
});
|
|
66
|
+
logger.info('Preflight compilation succeeded');
|
|
67
|
+
return { success: true };
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const stderr = err.stderr?.toString() ?? '';
|
|
71
|
+
const stdout = err.stdout?.toString() ?? '';
|
|
72
|
+
const output = (stderr + '\n' + stdout).trim();
|
|
73
|
+
const errors = output.split('\n').filter(Boolean);
|
|
74
|
+
logger.warn({ errorCount: errors.length }, 'Preflight compilation failed');
|
|
75
|
+
return { success: false, errors };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
logger.error({ err }, 'Preflight worktree setup failed');
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
errors: [`Worktree setup failed: ${String(err)}`],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
// 6. Always clean up the worktree
|
|
87
|
+
try {
|
|
88
|
+
execSync(`git worktree remove --force "${worktreePath}"`, {
|
|
89
|
+
cwd: pkgDir,
|
|
90
|
+
stdio: 'pipe',
|
|
91
|
+
});
|
|
92
|
+
logger.info('Preflight worktree cleaned up');
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Best effort — worktree prune will catch it later
|
|
96
|
+
logger.warn({ worktreePath }, 'Failed to remove preflight worktree — will be pruned later');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=source-preflight.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — StallGuard.
|
|
3
|
+
*
|
|
4
|
+
* Per-query stall detection and enforcement. Combines tool loop detection,
|
|
5
|
+
* metacognitive monitoring, and read-blocking into a single query-scoped
|
|
6
|
+
* instance. No global mutable state — concurrent queries get their own guard.
|
|
7
|
+
*
|
|
8
|
+
* Lifecycle:
|
|
9
|
+
* 1. Created before each SDK query
|
|
10
|
+
* 2. Passed to buildOptions() → canUseTool checks shouldBlockTool()
|
|
11
|
+
* 3. recordToolCall() called for each tool_use block in the stream
|
|
12
|
+
* 4. After query: detectPromiseWithoutAction() + getSummary() for cross-query nudges
|
|
13
|
+
*/
|
|
14
|
+
import { type MetacognitiveSignal, type MetacognitiveSummary } from './metacognition.js';
|
|
15
|
+
export interface StallSummary {
|
|
16
|
+
metacognition: MetacognitiveSummary;
|
|
17
|
+
breakerActivated: boolean;
|
|
18
|
+
breakerReason: string;
|
|
19
|
+
toolCalls: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare class StallGuard {
|
|
22
|
+
private loopDetector;
|
|
23
|
+
private metacog;
|
|
24
|
+
private breakerActive;
|
|
25
|
+
private breakerReason;
|
|
26
|
+
private toolCallLog;
|
|
27
|
+
/**
|
|
28
|
+
* Check if a tool should be blocked. Called from canUseTool.
|
|
29
|
+
* When the breaker is active, denies read-only tools to force the agent
|
|
30
|
+
* to either act (Write/Edit/Bash) or respond to the user.
|
|
31
|
+
*/
|
|
32
|
+
shouldBlockTool(toolName: string): {
|
|
33
|
+
block: boolean;
|
|
34
|
+
message?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Record a tool call. Runs loop detection and metacognition.
|
|
38
|
+
* Activates the breaker if either detector fires.
|
|
39
|
+
*
|
|
40
|
+
* If the breaker is already active and this is a read-only tool, it was
|
|
41
|
+
* denied by shouldBlockTool. Skip metacognition tracking for denied tools
|
|
42
|
+
* to prevent a feedback loop where denials inflate the consecutive-read
|
|
43
|
+
* counter (logs showed counter spiraling from 5 → 15 in milliseconds).
|
|
44
|
+
*/
|
|
45
|
+
recordToolCall(toolName: string, input: Record<string, unknown>): void;
|
|
46
|
+
/**
|
|
47
|
+
* Record a tool result for the loop detector's poll-no-progress check.
|
|
48
|
+
*/
|
|
49
|
+
recordToolResult(resultText: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Post-query: check if the response promises action without delivery.
|
|
52
|
+
*/
|
|
53
|
+
detectPromiseWithoutAction(responseText: string): MetacognitiveSignal;
|
|
54
|
+
/**
|
|
55
|
+
* Get summary for logging and cross-query stall nudge decisions.
|
|
56
|
+
*/
|
|
57
|
+
getSummary(): StallSummary;
|
|
58
|
+
/** Get tool call log for transcript auditing. */
|
|
59
|
+
getToolCalls(): string[];
|
|
60
|
+
private activate;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=stall-guard.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — StallGuard.
|
|
3
|
+
*
|
|
4
|
+
* Per-query stall detection and enforcement. Combines tool loop detection,
|
|
5
|
+
* metacognitive monitoring, and read-blocking into a single query-scoped
|
|
6
|
+
* instance. No global mutable state — concurrent queries get their own guard.
|
|
7
|
+
*
|
|
8
|
+
* Lifecycle:
|
|
9
|
+
* 1. Created before each SDK query
|
|
10
|
+
* 2. Passed to buildOptions() → canUseTool checks shouldBlockTool()
|
|
11
|
+
* 3. recordToolCall() called for each tool_use block in the stream
|
|
12
|
+
* 4. After query: detectPromiseWithoutAction() + getSummary() for cross-query nudges
|
|
13
|
+
*/
|
|
14
|
+
import { ToolLoopDetector } from './tool-loop-detector.js';
|
|
15
|
+
import { MetacognitiveMonitor } from './metacognition.js';
|
|
16
|
+
import pino from 'pino';
|
|
17
|
+
const logger = pino({ name: 'clementine.stall-guard' });
|
|
18
|
+
// Only block SDK read tools — MCP tools (memory_read, etc.) are intentionally
|
|
19
|
+
// left unblocked to give the agent some information access while forced to act.
|
|
20
|
+
const READ_ONLY_TOOLS = new Set(['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch']);
|
|
21
|
+
// ── StallGuard ──────────────────────────────────────────────────────
|
|
22
|
+
export class StallGuard {
|
|
23
|
+
loopDetector = new ToolLoopDetector();
|
|
24
|
+
metacog = new MetacognitiveMonitor();
|
|
25
|
+
breakerActive = false;
|
|
26
|
+
breakerReason = '';
|
|
27
|
+
toolCallLog = [];
|
|
28
|
+
/**
|
|
29
|
+
* Check if a tool should be blocked. Called from canUseTool.
|
|
30
|
+
* When the breaker is active, denies read-only tools to force the agent
|
|
31
|
+
* to either act (Write/Edit/Bash) or respond to the user.
|
|
32
|
+
*/
|
|
33
|
+
shouldBlockTool(toolName) {
|
|
34
|
+
if (this.breakerActive && READ_ONLY_TOOLS.has(toolName)) {
|
|
35
|
+
return {
|
|
36
|
+
block: true,
|
|
37
|
+
message: `STALL BREAKER: You have been reading without acting for too long. ${this.breakerReason} ` +
|
|
38
|
+
`STOP reading. Either perform a write/action tool call (Write, Edit, Bash, etc.) to complete the task, ` +
|
|
39
|
+
`or respond to the user explaining what is blocking you. Do NOT call another read-only tool.`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return { block: false };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Record a tool call. Runs loop detection and metacognition.
|
|
46
|
+
* Activates the breaker if either detector fires.
|
|
47
|
+
*
|
|
48
|
+
* If the breaker is already active and this is a read-only tool, it was
|
|
49
|
+
* denied by shouldBlockTool. Skip metacognition tracking for denied tools
|
|
50
|
+
* to prevent a feedback loop where denials inflate the consecutive-read
|
|
51
|
+
* counter (logs showed counter spiraling from 5 → 15 in milliseconds).
|
|
52
|
+
*/
|
|
53
|
+
recordToolCall(toolName, input) {
|
|
54
|
+
const wasDenied = this.breakerActive && READ_ONLY_TOOLS.has(toolName);
|
|
55
|
+
// Tool loop detector
|
|
56
|
+
const loopCheck = this.loopDetector.recordCall(toolName, input);
|
|
57
|
+
if (loopCheck.verdict === 'block') {
|
|
58
|
+
logger.warn({ tool: toolName, ...loopCheck }, 'Tool loop — activating stall breaker');
|
|
59
|
+
this.activate(loopCheck.detail ?? 'Repetitive tool calls detected.');
|
|
60
|
+
}
|
|
61
|
+
// Metacognitive monitor — only hard-block on 'intervene'.
|
|
62
|
+
// 'warn' logs and drops confidence but doesn't activate the breaker,
|
|
63
|
+
// so the agent can still read during legitimate multi-file research.
|
|
64
|
+
if (!wasDenied) {
|
|
65
|
+
const mcSignal = this.metacog.recordToolCall(toolName, input);
|
|
66
|
+
if (mcSignal.type === 'intervene') {
|
|
67
|
+
logger.warn({ reason: mcSignal.reason }, `Metacognition intervene: ${mcSignal.guidance?.slice(0, 80)}`);
|
|
68
|
+
this.activate(mcSignal.guidance ?? 'Agent appears stuck.');
|
|
69
|
+
}
|
|
70
|
+
else if (mcSignal.type === 'warn') {
|
|
71
|
+
logger.info({ reason: mcSignal.reason }, `Metacognition warn: ${mcSignal.guidance?.slice(0, 80)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Audit trail
|
|
75
|
+
this.toolCallLog.push(`${wasDenied ? '✗' : ''}${toolName}(${JSON.stringify(input).slice(0, 200)})`);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Record a tool result for the loop detector's poll-no-progress check.
|
|
79
|
+
*/
|
|
80
|
+
recordToolResult(resultText) {
|
|
81
|
+
this.loopDetector.recordResult(resultText);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Post-query: check if the response promises action without delivery.
|
|
85
|
+
*/
|
|
86
|
+
detectPromiseWithoutAction(responseText) {
|
|
87
|
+
return this.metacog.detectPromiseWithoutAction(responseText, this.toolCallLog.length);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get summary for logging and cross-query stall nudge decisions.
|
|
91
|
+
*/
|
|
92
|
+
getSummary() {
|
|
93
|
+
return {
|
|
94
|
+
metacognition: this.metacog.getSummary(),
|
|
95
|
+
breakerActivated: this.breakerActive,
|
|
96
|
+
breakerReason: this.breakerReason,
|
|
97
|
+
toolCalls: [...this.toolCallLog],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Get tool call log for transcript auditing. */
|
|
101
|
+
getToolCalls() {
|
|
102
|
+
return [...this.toolCallLog];
|
|
103
|
+
}
|
|
104
|
+
activate(reason) {
|
|
105
|
+
this.breakerActive = true;
|
|
106
|
+
this.breakerReason = reason;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=stall-guard.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Strategic Planner (Multi-Horizon Planning).
|
|
3
|
+
*
|
|
4
|
+
* Weekly reviews: synthesize daily plans + goal progress into accomplishments,
|
|
5
|
+
* missed targets, patterns, and recommendations.
|
|
6
|
+
*
|
|
7
|
+
* Monthly assessments: cross-reference weekly reviews with goal completion
|
|
8
|
+
* and self-improvement experiments. Proposes OKR-style goals.
|
|
9
|
+
*
|
|
10
|
+
* Plans are persisted to ~/.clementine/plans/weekly/ and monthly/.
|
|
11
|
+
*/
|
|
12
|
+
import type { DailyPlan } from '../types.js';
|
|
13
|
+
export interface WeeklyReview {
|
|
14
|
+
weekId: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
accomplishments: string[];
|
|
17
|
+
missedTargets: string[];
|
|
18
|
+
patterns: string[];
|
|
19
|
+
recommendations: string[];
|
|
20
|
+
goalProgress: Array<{
|
|
21
|
+
goalId: string;
|
|
22
|
+
title: string;
|
|
23
|
+
status: string;
|
|
24
|
+
noteCount: number;
|
|
25
|
+
}>;
|
|
26
|
+
summary: string;
|
|
27
|
+
}
|
|
28
|
+
export interface MonthlyAssessment {
|
|
29
|
+
monthId: string;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
weeklyTrends: string[];
|
|
32
|
+
goalCompletionRate: number;
|
|
33
|
+
systemicIssues: string[];
|
|
34
|
+
proposedGoals: Array<{
|
|
35
|
+
title: string;
|
|
36
|
+
description: string;
|
|
37
|
+
priority: string;
|
|
38
|
+
}>;
|
|
39
|
+
summary: string;
|
|
40
|
+
}
|
|
41
|
+
export declare class StrategicPlanner {
|
|
42
|
+
constructor();
|
|
43
|
+
hasWeeklyReview(weekId?: string): boolean;
|
|
44
|
+
generateWeeklyReview(): Promise<WeeklyReview>;
|
|
45
|
+
private gatherWeeklyContext;
|
|
46
|
+
private callLlmForWeekly;
|
|
47
|
+
hasMonthlyAssessment(monthId?: string): boolean;
|
|
48
|
+
generateMonthlyAssessment(): Promise<MonthlyAssessment>;
|
|
49
|
+
private gatherMonthlyContext;
|
|
50
|
+
private callLlmForMonthly;
|
|
51
|
+
/**
|
|
52
|
+
* Check if today's daily plan aligns with active high-priority goals.
|
|
53
|
+
* Returns a warning string if misaligned, null if fine.
|
|
54
|
+
*/
|
|
55
|
+
checkGoalPlanAlignment(dailyPlan: DailyPlan): string | null;
|
|
56
|
+
private loadRecentDailyPlans;
|
|
57
|
+
private loadActiveGoals;
|
|
58
|
+
private loadAllGoals;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=strategic-planner.d.ts.map
|