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.
Files changed (190) hide show
  1. package/.env.example +44 -0
  2. package/LICENSE +21 -0
  3. package/README.md +795 -0
  4. package/dist/agent/agent-manager.d.ts +69 -0
  5. package/dist/agent/agent-manager.js +441 -0
  6. package/dist/agent/assistant.d.ts +225 -0
  7. package/dist/agent/assistant.js +3888 -0
  8. package/dist/agent/auto-update.d.ts +32 -0
  9. package/dist/agent/auto-update.js +186 -0
  10. package/dist/agent/daily-planner.d.ts +24 -0
  11. package/dist/agent/daily-planner.js +379 -0
  12. package/dist/agent/execution-advisor.d.ts +10 -0
  13. package/dist/agent/execution-advisor.js +272 -0
  14. package/dist/agent/hooks.d.ts +45 -0
  15. package/dist/agent/hooks.js +564 -0
  16. package/dist/agent/insight-engine.d.ts +66 -0
  17. package/dist/agent/insight-engine.js +225 -0
  18. package/dist/agent/intent-classifier.d.ts +48 -0
  19. package/dist/agent/intent-classifier.js +214 -0
  20. package/dist/agent/link-extractor.d.ts +19 -0
  21. package/dist/agent/link-extractor.js +90 -0
  22. package/dist/agent/mcp-bridge.d.ts +62 -0
  23. package/dist/agent/mcp-bridge.js +435 -0
  24. package/dist/agent/metacognition.d.ts +66 -0
  25. package/dist/agent/metacognition.js +221 -0
  26. package/dist/agent/orchestrator.d.ts +81 -0
  27. package/dist/agent/orchestrator.js +790 -0
  28. package/dist/agent/profiles.d.ts +22 -0
  29. package/dist/agent/profiles.js +91 -0
  30. package/dist/agent/prompt-cache.d.ts +24 -0
  31. package/dist/agent/prompt-cache.js +68 -0
  32. package/dist/agent/prompt-evolver.d.ts +28 -0
  33. package/dist/agent/prompt-evolver.js +279 -0
  34. package/dist/agent/role-scaffolds.d.ts +28 -0
  35. package/dist/agent/role-scaffolds.js +433 -0
  36. package/dist/agent/safe-restart.d.ts +41 -0
  37. package/dist/agent/safe-restart.js +150 -0
  38. package/dist/agent/self-improve.d.ts +66 -0
  39. package/dist/agent/self-improve.js +1706 -0
  40. package/dist/agent/session-event-log.d.ts +114 -0
  41. package/dist/agent/session-event-log.js +233 -0
  42. package/dist/agent/skill-extractor.d.ts +72 -0
  43. package/dist/agent/skill-extractor.js +435 -0
  44. package/dist/agent/source-mods.d.ts +61 -0
  45. package/dist/agent/source-mods.js +230 -0
  46. package/dist/agent/source-preflight.d.ts +25 -0
  47. package/dist/agent/source-preflight.js +100 -0
  48. package/dist/agent/stall-guard.d.ts +62 -0
  49. package/dist/agent/stall-guard.js +109 -0
  50. package/dist/agent/strategic-planner.d.ts +60 -0
  51. package/dist/agent/strategic-planner.js +352 -0
  52. package/dist/agent/team-bus.d.ts +89 -0
  53. package/dist/agent/team-bus.js +556 -0
  54. package/dist/agent/team-router.d.ts +26 -0
  55. package/dist/agent/team-router.js +37 -0
  56. package/dist/agent/tool-loop-detector.d.ts +59 -0
  57. package/dist/agent/tool-loop-detector.js +242 -0
  58. package/dist/agent/workflow-runner.d.ts +36 -0
  59. package/dist/agent/workflow-runner.js +317 -0
  60. package/dist/agent/workflow-variables.d.ts +16 -0
  61. package/dist/agent/workflow-variables.js +62 -0
  62. package/dist/channels/discord-agent-bot.d.ts +101 -0
  63. package/dist/channels/discord-agent-bot.js +881 -0
  64. package/dist/channels/discord-bot-manager.d.ts +80 -0
  65. package/dist/channels/discord-bot-manager.js +262 -0
  66. package/dist/channels/discord-utils.d.ts +51 -0
  67. package/dist/channels/discord-utils.js +293 -0
  68. package/dist/channels/discord.d.ts +12 -0
  69. package/dist/channels/discord.js +1832 -0
  70. package/dist/channels/slack-agent-bot.d.ts +73 -0
  71. package/dist/channels/slack-agent-bot.js +320 -0
  72. package/dist/channels/slack-bot-manager.d.ts +66 -0
  73. package/dist/channels/slack-bot-manager.js +236 -0
  74. package/dist/channels/slack-utils.d.ts +39 -0
  75. package/dist/channels/slack-utils.js +189 -0
  76. package/dist/channels/slack.d.ts +11 -0
  77. package/dist/channels/slack.js +196 -0
  78. package/dist/channels/telegram.d.ts +10 -0
  79. package/dist/channels/telegram.js +235 -0
  80. package/dist/channels/webhook.d.ts +9 -0
  81. package/dist/channels/webhook.js +78 -0
  82. package/dist/channels/whatsapp.d.ts +11 -0
  83. package/dist/channels/whatsapp.js +181 -0
  84. package/dist/cli/chat.d.ts +14 -0
  85. package/dist/cli/chat.js +220 -0
  86. package/dist/cli/cron.d.ts +17 -0
  87. package/dist/cli/cron.js +552 -0
  88. package/dist/cli/dashboard.d.ts +15 -0
  89. package/dist/cli/dashboard.js +17677 -0
  90. package/dist/cli/index.d.ts +3 -0
  91. package/dist/cli/index.js +2474 -0
  92. package/dist/cli/routes/delegations.d.ts +19 -0
  93. package/dist/cli/routes/delegations.js +154 -0
  94. package/dist/cli/routes/digest.d.ts +17 -0
  95. package/dist/cli/routes/digest.js +375 -0
  96. package/dist/cli/routes/goals.d.ts +14 -0
  97. package/dist/cli/routes/goals.js +258 -0
  98. package/dist/cli/routes/workflows.d.ts +18 -0
  99. package/dist/cli/routes/workflows.js +97 -0
  100. package/dist/cli/setup.d.ts +8 -0
  101. package/dist/cli/setup.js +619 -0
  102. package/dist/cli/tunnel.d.ts +35 -0
  103. package/dist/cli/tunnel.js +141 -0
  104. package/dist/config.d.ts +145 -0
  105. package/dist/config.js +278 -0
  106. package/dist/events/bus.d.ts +43 -0
  107. package/dist/events/bus.js +136 -0
  108. package/dist/gateway/cron-scheduler.d.ts +166 -0
  109. package/dist/gateway/cron-scheduler.js +1767 -0
  110. package/dist/gateway/delivery-queue.d.ts +30 -0
  111. package/dist/gateway/delivery-queue.js +110 -0
  112. package/dist/gateway/heartbeat-scheduler.d.ts +99 -0
  113. package/dist/gateway/heartbeat-scheduler.js +1298 -0
  114. package/dist/gateway/heartbeat.d.ts +3 -0
  115. package/dist/gateway/heartbeat.js +3 -0
  116. package/dist/gateway/lanes.d.ts +24 -0
  117. package/dist/gateway/lanes.js +76 -0
  118. package/dist/gateway/notifications.d.ts +29 -0
  119. package/dist/gateway/notifications.js +75 -0
  120. package/dist/gateway/router.d.ts +210 -0
  121. package/dist/gateway/router.js +1330 -0
  122. package/dist/index.d.ts +12 -0
  123. package/dist/index.js +1015 -0
  124. package/dist/memory/chunker.d.ts +28 -0
  125. package/dist/memory/chunker.js +226 -0
  126. package/dist/memory/consolidation.d.ts +44 -0
  127. package/dist/memory/consolidation.js +171 -0
  128. package/dist/memory/context-assembler.d.ts +50 -0
  129. package/dist/memory/context-assembler.js +149 -0
  130. package/dist/memory/embeddings.d.ts +38 -0
  131. package/dist/memory/embeddings.js +180 -0
  132. package/dist/memory/graph-store.d.ts +66 -0
  133. package/dist/memory/graph-store.js +613 -0
  134. package/dist/memory/mmr.d.ts +21 -0
  135. package/dist/memory/mmr.js +75 -0
  136. package/dist/memory/search.d.ts +26 -0
  137. package/dist/memory/search.js +67 -0
  138. package/dist/memory/store.d.ts +530 -0
  139. package/dist/memory/store.js +2022 -0
  140. package/dist/security/integrity.d.ts +24 -0
  141. package/dist/security/integrity.js +58 -0
  142. package/dist/security/patterns.d.ts +34 -0
  143. package/dist/security/patterns.js +110 -0
  144. package/dist/security/scanner.d.ts +32 -0
  145. package/dist/security/scanner.js +263 -0
  146. package/dist/tools/admin-tools.d.ts +12 -0
  147. package/dist/tools/admin-tools.js +1278 -0
  148. package/dist/tools/external-tools.d.ts +11 -0
  149. package/dist/tools/external-tools.js +1327 -0
  150. package/dist/tools/goal-tools.d.ts +9 -0
  151. package/dist/tools/goal-tools.js +159 -0
  152. package/dist/tools/mcp-server.d.ts +13 -0
  153. package/dist/tools/mcp-server.js +141 -0
  154. package/dist/tools/memory-tools.d.ts +10 -0
  155. package/dist/tools/memory-tools.js +568 -0
  156. package/dist/tools/session-tools.d.ts +6 -0
  157. package/dist/tools/session-tools.js +146 -0
  158. package/dist/tools/shared.d.ts +216 -0
  159. package/dist/tools/shared.js +340 -0
  160. package/dist/tools/team-tools.d.ts +6 -0
  161. package/dist/tools/team-tools.js +447 -0
  162. package/dist/tools/tool-meta.d.ts +34 -0
  163. package/dist/tools/tool-meta.js +133 -0
  164. package/dist/tools/vault-tools.d.ts +8 -0
  165. package/dist/tools/vault-tools.js +457 -0
  166. package/dist/types.d.ts +716 -0
  167. package/dist/types.js +16 -0
  168. package/dist/vault-migrations/0001-add-execution-framework.d.ts +10 -0
  169. package/dist/vault-migrations/0001-add-execution-framework.js +47 -0
  170. package/dist/vault-migrations/0002-add-agentic-communication.d.ts +12 -0
  171. package/dist/vault-migrations/0002-add-agentic-communication.js +79 -0
  172. package/dist/vault-migrations/0003-update-execution-pipeline-narration.d.ts +11 -0
  173. package/dist/vault-migrations/0003-update-execution-pipeline-narration.js +73 -0
  174. package/dist/vault-migrations/helpers.d.ts +14 -0
  175. package/dist/vault-migrations/helpers.js +44 -0
  176. package/dist/vault-migrations/runner.d.ts +14 -0
  177. package/dist/vault-migrations/runner.js +139 -0
  178. package/dist/vault-migrations/types.d.ts +42 -0
  179. package/dist/vault-migrations/types.js +9 -0
  180. package/install.sh +320 -0
  181. package/package.json +84 -0
  182. package/scripts/postinstall.js +125 -0
  183. package/vault/00-System/AGENTS.md +66 -0
  184. package/vault/00-System/CRON.md +71 -0
  185. package/vault/00-System/HEARTBEAT.md +58 -0
  186. package/vault/00-System/MEMORY.md +16 -0
  187. package/vault/00-System/SOUL.md +96 -0
  188. package/vault/05-Tasks/TASKS.md +19 -0
  189. package/vault/06-Templates/_Daily-Template.md +28 -0
  190. 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