overarch-linux-arm64 0.8.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/dist/bin/overarch.d.ts +2 -0
  4. package/dist/bin/overarch.js +178 -0
  5. package/dist/src/agent/context.d.ts +14 -0
  6. package/dist/src/agent/context.js +200 -0
  7. package/dist/src/agent/context_compressor.d.ts +21 -0
  8. package/dist/src/agent/context_compressor.js +90 -0
  9. package/dist/src/agent/manifest.d.ts +22 -0
  10. package/dist/src/agent/manifest.js +131 -0
  11. package/dist/src/agent/planner.d.ts +38 -0
  12. package/dist/src/agent/planner.js +118 -0
  13. package/dist/src/agent/runtime.d.ts +59 -0
  14. package/dist/src/agent/runtime.js +400 -0
  15. package/dist/src/cli/approval.d.ts +13 -0
  16. package/dist/src/cli/approval.js +71 -0
  17. package/dist/src/cli/args.d.ts +15 -0
  18. package/dist/src/cli/args.js +47 -0
  19. package/dist/src/cli/commands/audit.d.ts +10 -0
  20. package/dist/src/cli/commands/audit.js +79 -0
  21. package/dist/src/cli/commands/companion.d.ts +1 -0
  22. package/dist/src/cli/commands/companion.js +143 -0
  23. package/dist/src/cli/commands/config.d.ts +1 -0
  24. package/dist/src/cli/commands/config.js +135 -0
  25. package/dist/src/cli/commands/doctor.d.ts +1 -0
  26. package/dist/src/cli/commands/doctor.js +217 -0
  27. package/dist/src/cli/commands/init.d.ts +1 -0
  28. package/dist/src/cli/commands/init.js +134 -0
  29. package/dist/src/cli/commands/memory.d.ts +2 -0
  30. package/dist/src/cli/commands/memory.js +98 -0
  31. package/dist/src/cli/commands/onboarding.d.ts +3 -0
  32. package/dist/src/cli/commands/onboarding.js +223 -0
  33. package/dist/src/cli/commands/sessions.d.ts +1 -0
  34. package/dist/src/cli/commands/sessions.js +22 -0
  35. package/dist/src/cli/commands/status.d.ts +1 -0
  36. package/dist/src/cli/commands/status.js +37 -0
  37. package/dist/src/cli/commands/update.d.ts +9 -0
  38. package/dist/src/cli/commands/update.js +105 -0
  39. package/dist/src/cli/repl.d.ts +3 -0
  40. package/dist/src/cli/repl.js +821 -0
  41. package/dist/src/cli/ui.d.ts +53 -0
  42. package/dist/src/cli/ui.js +215 -0
  43. package/dist/src/companion/petdex_adapter.d.ts +31 -0
  44. package/dist/src/companion/petdex_adapter.js +171 -0
  45. package/dist/src/companion/petdex_bridge.d.ts +65 -0
  46. package/dist/src/companion/petdex_bridge.js +356 -0
  47. package/dist/src/companion/types.d.ts +57 -0
  48. package/dist/src/companion/types.js +11 -0
  49. package/dist/src/config/index.d.ts +24 -0
  50. package/dist/src/config/index.js +237 -0
  51. package/dist/src/config/types.d.ts +58 -0
  52. package/dist/src/config/types.js +7 -0
  53. package/dist/src/core/diagnostics.d.ts +48 -0
  54. package/dist/src/core/diagnostics.js +115 -0
  55. package/dist/src/core/index.d.ts +23 -0
  56. package/dist/src/core/index.js +23 -0
  57. package/dist/src/core/mission.d.ts +55 -0
  58. package/dist/src/core/mission.js +173 -0
  59. package/dist/src/index.d.ts +1 -0
  60. package/dist/src/index.js +1 -0
  61. package/dist/src/learning/memory_manager.d.ts +26 -0
  62. package/dist/src/learning/memory_manager.js +211 -0
  63. package/dist/src/learning/types.d.ts +54 -0
  64. package/dist/src/learning/types.js +1 -0
  65. package/dist/src/mcp/registry.d.ts +17 -0
  66. package/dist/src/mcp/registry.js +97 -0
  67. package/dist/src/mcp/types.d.ts +13 -0
  68. package/dist/src/mcp/types.js +1 -0
  69. package/dist/src/providers/anthropic.d.ts +10 -0
  70. package/dist/src/providers/anthropic.js +95 -0
  71. package/dist/src/providers/base.d.ts +15 -0
  72. package/dist/src/providers/base.js +108 -0
  73. package/dist/src/providers/factory.d.ts +28 -0
  74. package/dist/src/providers/factory.js +142 -0
  75. package/dist/src/providers/gemini.d.ts +10 -0
  76. package/dist/src/providers/gemini.js +97 -0
  77. package/dist/src/providers/openai.d.ts +9 -0
  78. package/dist/src/providers/openai.js +85 -0
  79. package/dist/src/providers/openrouter.d.ts +5 -0
  80. package/dist/src/providers/openrouter.js +14 -0
  81. package/dist/src/providers/registry.d.ts +19 -0
  82. package/dist/src/providers/registry.js +209 -0
  83. package/dist/src/providers/types.d.ts +59 -0
  84. package/dist/src/providers/types.js +1 -0
  85. package/dist/src/providers/validator.d.ts +24 -0
  86. package/dist/src/providers/validator.js +161 -0
  87. package/dist/src/security/audit.d.ts +56 -0
  88. package/dist/src/security/audit.js +154 -0
  89. package/dist/src/security/guardrails.d.ts +9 -0
  90. package/dist/src/security/guardrails.js +32 -0
  91. package/dist/src/security/permissions.d.ts +12 -0
  92. package/dist/src/security/permissions.js +68 -0
  93. package/dist/src/security/policy.d.ts +220 -0
  94. package/dist/src/security/policy.js +720 -0
  95. package/dist/src/session/manager.d.ts +22 -0
  96. package/dist/src/session/manager.js +172 -0
  97. package/dist/src/session/types.d.ts +36 -0
  98. package/dist/src/session/types.js +1 -0
  99. package/dist/src/tools/checkpoint.d.ts +26 -0
  100. package/dist/src/tools/checkpoint.js +160 -0
  101. package/dist/src/tools/diagnostics.d.ts +2 -0
  102. package/dist/src/tools/diagnostics.js +125 -0
  103. package/dist/src/tools/diff.d.ts +14 -0
  104. package/dist/src/tools/diff.js +124 -0
  105. package/dist/src/tools/filesystem.d.ts +2 -0
  106. package/dist/src/tools/filesystem.js +224 -0
  107. package/dist/src/tools/git.d.ts +2 -0
  108. package/dist/src/tools/git.js +138 -0
  109. package/dist/src/tools/markdown.d.ts +33 -0
  110. package/dist/src/tools/markdown.js +190 -0
  111. package/dist/src/tools/project.d.ts +2 -0
  112. package/dist/src/tools/project.js +63 -0
  113. package/dist/src/tools/registry.d.ts +14 -0
  114. package/dist/src/tools/registry.js +230 -0
  115. package/dist/src/tools/search.d.ts +2 -0
  116. package/dist/src/tools/search.js +136 -0
  117. package/dist/src/tools/shell.d.ts +2 -0
  118. package/dist/src/tools/shell.js +74 -0
  119. package/dist/src/tools/symbols.d.ts +30 -0
  120. package/dist/src/tools/symbols.js +155 -0
  121. package/dist/src/tools/types.d.ts +28 -0
  122. package/dist/src/tools/types.js +1 -0
  123. package/dist/src/tools/web_search.d.ts +14 -0
  124. package/dist/src/tools/web_search.js +154 -0
  125. package/install.sh +29 -0
  126. package/overarch +15 -0
  127. package/package.json +28 -0
@@ -0,0 +1,821 @@
1
+ import * as readline from 'node:readline/promises';
2
+ import { stdin, stdout } from 'node:process';
3
+ import * as path from 'node:path';
4
+ import * as fs from 'node:fs/promises';
5
+ import { styleText, promisify } from 'node:util';
6
+ import { exec } from 'node:child_process';
7
+ import { SessionManager } from '../session/manager.js';
8
+ import { AgentRuntime } from '../agent/runtime.js';
9
+ import { buildContext, inspectProjectContext } from '../agent/context.js';
10
+ import { ToolRegistry } from '../tools/registry.js';
11
+ import { resetAbort } from '../security/guardrails.js';
12
+ import { createProvider, resolveModel, listModels, OfflineProvider } from '../providers/factory.js';
13
+ import { runSetup } from './commands/onboarding.js';
14
+ import { doctor } from './commands/doctor.js';
15
+ import { init } from './commands/init.js';
16
+ import { status } from './commands/status.js';
17
+ import { handleConfigCommand } from './commands/config.js';
18
+ import { handleMemoryCommand, handleLearnCommand } from './commands/memory.js';
19
+ import { handleCompanionCommand } from './commands/companion.js';
20
+ import { loadConfig, saveGlobalConfig, autoEnsureOverarch, getApiKey } from '../config/index.js';
21
+ import { petdexBridge } from '../companion/petdex_bridge.js';
22
+ import { parseCcssCommand, CCSS_MODE_CONFIGS } from '../agent/planner.js';
23
+ import { CheckpointManager } from '../tools/checkpoint.js';
24
+ import * as ui from './ui.js';
25
+ const execAsync = promisify(exec);
26
+ function resolveRuntimeProvider(config, modelOverride) {
27
+ if (config.mode === 'offline') {
28
+ return {
29
+ provider: new OfflineProvider(),
30
+ model: 'offline',
31
+ displayName: 'Offline Mode (AI disabled)',
32
+ isOffline: true
33
+ };
34
+ }
35
+ const modelId = modelOverride || config.defaultModel;
36
+ let resolved = modelId ? resolveModel(modelId) : undefined;
37
+ // 1. If an explicit model is requested or configured, try initializing it
38
+ if (resolved && resolved.provider !== 'none') {
39
+ const hasKey = config.providers[resolved.provider]?.apiKey || getApiKey(resolved.provider);
40
+ if (hasKey) {
41
+ try {
42
+ const providerConfig = config.providers[resolved.provider] || {};
43
+ const provider = createProvider(resolved.provider, providerConfig);
44
+ return { provider, model: resolved.model, displayName: resolved.displayName, isOffline: false };
45
+ }
46
+ catch (err) {
47
+ ui.warn(`Failed to initialize provider '${resolved.provider}': ${err.message}`);
48
+ }
49
+ }
50
+ }
51
+ // 2. Fallback: Automatically check environment for any available provider key!
52
+ // Priority: OpenRouter (multi-provider), Anthropic, OpenAI, Gemini
53
+ const providerCandidates = [
54
+ { id: 'openrouter', defaultModel: 'openrouter/auto', name: 'OpenRouter' },
55
+ { id: 'anthropic', defaultModel: 'claude-3-5-sonnet-20241022', name: 'Anthropic' },
56
+ { id: 'openai', defaultModel: 'gpt-4o', name: 'OpenAI' },
57
+ { id: 'gemini', defaultModel: 'gemini-2.5-flash', name: 'Google Gemini' }
58
+ ];
59
+ for (const candidate of providerCandidates) {
60
+ const key = config.providers[candidate.id]?.apiKey || getApiKey(candidate.id);
61
+ if (key) {
62
+ try {
63
+ const provider = createProvider(candidate.id, config.providers[candidate.id] || {});
64
+ const autoResolved = resolveModel(candidate.defaultModel, candidate.id) || {
65
+ provider: candidate.id,
66
+ model: candidate.defaultModel,
67
+ displayName: `${candidate.name} (${candidate.defaultModel})`
68
+ };
69
+ config.defaultModel = autoResolved.model;
70
+ if (modelId && resolved && resolved.provider !== candidate.id) {
71
+ ui.info(`Provider '${resolved.provider}' key not detected. Automatically using active ${candidate.name} key.`);
72
+ }
73
+ return {
74
+ provider,
75
+ model: autoResolved.model,
76
+ displayName: autoResolved.displayName,
77
+ isOffline: false
78
+ };
79
+ }
80
+ catch { }
81
+ }
82
+ }
83
+ // 3. Clean offline fallback when no provider keys exist
84
+ return {
85
+ provider: new OfflineProvider(),
86
+ model: 'offline',
87
+ displayName: 'Offline Mode (No API key found — run /setup)',
88
+ isOffline: true
89
+ };
90
+ }
91
+ export async function startRepl(initialConfig, args) {
92
+ let config = initialConfig;
93
+ // Apply UI accent and avatar from config
94
+ if (config.ui?.accentColor) {
95
+ ui.setAccent(config.ui.accentColor);
96
+ }
97
+ if (config.ui?.avatar) {
98
+ ui.setAvatar(config.ui.avatar);
99
+ }
100
+ let cwd = process.cwd();
101
+ await autoEnsureOverarch(cwd);
102
+ // Initialize optional Petdex companion bridge (non-blocking)
103
+ await petdexBridge.start().catch(() => { });
104
+ let checkpointManager = new CheckpointManager(cwd);
105
+ let sessionManager = new SessionManager(undefined, cwd);
106
+ let providerResult = resolveRuntimeProvider(config, args.model);
107
+ // Session resolution: resume by ID prefix or latest
108
+ let session;
109
+ if (args.resume) {
110
+ if (args.prompt && /^[0-9a-f-]{4,36}$/i.test(args.prompt.trim())) {
111
+ session = await sessionManager.findSession(args.prompt.trim());
112
+ if (session) {
113
+ args.prompt = undefined;
114
+ }
115
+ }
116
+ if (!session) {
117
+ session = await sessionManager.latest();
118
+ }
119
+ }
120
+ if (!session) {
121
+ session = await sessionManager.create(cwd, providerResult.model);
122
+ }
123
+ else {
124
+ ui.info(`Resumed session ${ui.bold(session.id.slice(0, 8))} (${session.messages.length} messages)`);
125
+ }
126
+ const rl = readline.createInterface({ input: stdin, output: stdout });
127
+ const registry = ToolRegistry.createDefault();
128
+ let agentRuntime = new AgentRuntime(providerResult.provider, registry, {
129
+ ...config,
130
+ defaultModel: providerResult.model,
131
+ }, { autoApprove: args.yes || config.agentMode === 'autonomous', rl, cwd });
132
+ if (session && session.messages.length > 0) {
133
+ const pastMessages = session.messages.map(m => ({
134
+ role: m.role,
135
+ content: m.content,
136
+ toolCallId: m.toolCallId,
137
+ toolCalls: m.toolCalls
138
+ }));
139
+ agentRuntime.setMessages(pastMessages);
140
+ }
141
+ let systemContext = await buildContext(cwd, config);
142
+ // Clean Header
143
+ console.log();
144
+ ui.banner();
145
+ console.log(styleText('dim', '────────────────────────────────────────'));
146
+ console.log(` ${styleText('dim', 'Project:')} ${cwd}`);
147
+ console.log(` ${styleText('dim', 'Model:')} ${providerResult.displayName} ${styleText('dim', `(${providerResult.model})`)}`);
148
+ console.log(` ${styleText('dim', 'Session:')} ${session.id.slice(0, 8)} ${session.messages.length > 0 ? styleText('dim', '(resumed)') : styleText('dim', '(new)')}`);
149
+ console.log(` ${styleText('dim', 'Agent Mode:')} ${config.agentMode} | CCSS: ${config.ccss} | Ponytail: ${config.ponytail}`);
150
+ console.log(styleText('dim', '────────────────────────────────────────'));
151
+ console.log(`${ui.dim('Type')} /help ${ui.dim('for categorized commands, or enter a prompt to begin.\n')}`);
152
+ let isGenerating = false;
153
+ let isExiting = false;
154
+ process.on('SIGINT', async () => {
155
+ if (isGenerating) {
156
+ agentRuntime.abort();
157
+ console.log(`\n${ui.yellow('!')} Operation aborted.`);
158
+ isGenerating = false;
159
+ }
160
+ else {
161
+ if (!isExiting) {
162
+ isExiting = true;
163
+ try {
164
+ await sessionManager.save(session);
165
+ }
166
+ catch { }
167
+ console.log(`\n${ui.accent(ui.getAvatar())} Session saved. Goodbye!`);
168
+ process.exit(0);
169
+ }
170
+ }
171
+ });
172
+ const toolTimers = new Map();
173
+ let thinkingSpin = null;
174
+ const stopThinking = () => {
175
+ if (thinkingSpin) {
176
+ thinkingSpin.stop();
177
+ thinkingSpin = null;
178
+ }
179
+ };
180
+ const callbacks = {
181
+ onText: (chunk) => {
182
+ stopThinking();
183
+ process.stdout.write(chunk);
184
+ },
185
+ onToolStart: (name, params) => {
186
+ stopThinking();
187
+ const summary = Object.entries(params).map(([k, v]) => {
188
+ const val = typeof v === 'string' ? (v.length > 40 ? v.slice(0, 40) + '...' : v) : JSON.stringify(v);
189
+ return `${k}=${val}`;
190
+ }).join(' ');
191
+ toolTimers.set(name, performance.now());
192
+ ui.toolStart(name, summary);
193
+ },
194
+ onToolEnd: (name, result) => {
195
+ const start = toolTimers.get(name);
196
+ const ms = start ? Math.round(performance.now() - start) : 0;
197
+ ui.toolEnd(name, result.success, ms);
198
+ if (result.metadata?.diff) {
199
+ const diffOutput = ui.formatDiff(String(result.metadata.diff));
200
+ if (diffOutput) {
201
+ console.log(diffOutput);
202
+ }
203
+ }
204
+ if (!result.success && result.error) {
205
+ ui.fail(`${name}: ${result.error}`);
206
+ }
207
+ // Resume thinking spinner while LLM processes tool result
208
+ thinkingSpin = ui.thinkingSpinner();
209
+ },
210
+ onPlan: (plan) => {
211
+ stopThinking();
212
+ console.log();
213
+ ui.heading('Task Roadmap');
214
+ plan.steps.forEach((s, i) => {
215
+ const status = s.status === 'done' ? 'done' : s.status === 'failed' ? 'failed' : s.status === 'running' ? 'in-progress' : 'pending';
216
+ ui.planStep(i + 1, s.description, status);
217
+ });
218
+ console.log();
219
+ },
220
+ onError: (error) => {
221
+ stopThinking();
222
+ ui.error(error.message);
223
+ },
224
+ onStateChange: (newState, message) => {
225
+ petdexBridge.broadcastState(newState, message);
226
+ if (newState === 'thinking') {
227
+ if (!thinkingSpin) {
228
+ thinkingSpin = ui.thinkingSpinner(message);
229
+ }
230
+ else if (message) {
231
+ thinkingSpin.setAction(message);
232
+ }
233
+ }
234
+ else {
235
+ stopThinking();
236
+ }
237
+ },
238
+ onReport: (report) => {
239
+ stopThinking();
240
+ if (report.filesModified.length > 0) {
241
+ console.log(`\n${ui.dim('◆')} ${ui.accent('Changes:')} ${report.filesModified.length} file(s) modified ${ui.dim(`(${report.verificationStatus})`)}`);
242
+ }
243
+ }
244
+ };
245
+ const runAgent = async (promptText) => {
246
+ if (providerResult.isOffline) {
247
+ ui.warn('Offline Mode is active. AI completions are disabled.');
248
+ console.log(ui.dim('Run /setup or /config provider to connect an AI provider.'));
249
+ return;
250
+ }
251
+ isGenerating = true;
252
+ resetAbort();
253
+ sessionManager.addMessage(session, {
254
+ role: 'user',
255
+ content: promptText,
256
+ timestamp: new Date().toISOString(),
257
+ });
258
+ console.log(`\n${ui.dim(`─── ${ui.getAvatar()} Overarch ──────────────────────────────`)}`);
259
+ const start = performance.now();
260
+ try {
261
+ thinkingSpin = ui.thinkingSpinner();
262
+ await agentRuntime.run(promptText, systemContext, callbacks);
263
+ }
264
+ catch (err) {
265
+ stopThinking();
266
+ if (err.name !== 'AbortError' && err.message !== 'Aborted') {
267
+ ui.error(`\nProvider Error: ${err.message}`);
268
+ }
269
+ }
270
+ finally {
271
+ stopThinking();
272
+ }
273
+ const elapsed = ((performance.now() - start) / 1000).toFixed(1);
274
+ console.log(`\n${ui.dim(`──────────────────────────────────── (${elapsed}s)`)}\n`);
275
+ // Sync messages
276
+ const currentMessages = agentRuntime.getMessages();
277
+ const newMessages = currentMessages.slice(session.messages.length);
278
+ for (const msg of newMessages) {
279
+ sessionManager.addMessage(session, {
280
+ role: msg.role,
281
+ content: msg.content,
282
+ toolCallId: msg.toolCallId,
283
+ toolCalls: msg.toolCalls,
284
+ timestamp: new Date().toISOString(),
285
+ });
286
+ }
287
+ await sessionManager.save(session);
288
+ isGenerating = false;
289
+ };
290
+ if (args.prompt) {
291
+ ui.userMessage(args.prompt);
292
+ await runAgent(args.prompt);
293
+ }
294
+ while (!isExiting) {
295
+ let input;
296
+ try {
297
+ input = await rl.question(ui.prompt());
298
+ }
299
+ catch {
300
+ break;
301
+ }
302
+ const command = input.trim();
303
+ if (!command)
304
+ continue;
305
+ // ─────────────────────────────────────────────────────────────
306
+ // 1. SESSION COMMANDS
307
+ // ─────────────────────────────────────────────────────────────
308
+ if (command === '/exit' || command === '/quit') {
309
+ await sessionManager.save(session);
310
+ ui.success('Session saved. Goodbye!');
311
+ isExiting = true;
312
+ break;
313
+ }
314
+ if (command === '/clear') {
315
+ agentRuntime.setMessages([]);
316
+ session.messages = [];
317
+ await sessionManager.save(session);
318
+ ui.info('Conversation history cleared.');
319
+ continue;
320
+ }
321
+ if (command === '/new') {
322
+ await sessionManager.save(session);
323
+ session = await sessionManager.create(cwd, providerResult.model);
324
+ agentRuntime.setMessages([]);
325
+ ui.success(`Created fresh session: ${ui.bold(session.id.slice(0, 8))}`);
326
+ continue;
327
+ }
328
+ if (command.startsWith('/resume')) {
329
+ const targetId = command.replace(/^\/resume\s*/, '').trim();
330
+ const targetSession = targetId ? await sessionManager.findSession(targetId) : await sessionManager.latest();
331
+ if (!targetSession) {
332
+ ui.warn(`No session matching '${targetId || 'latest'}' found.`);
333
+ continue;
334
+ }
335
+ await sessionManager.save(session);
336
+ session = targetSession;
337
+ agentRuntime.setMessages(session.messages.map(m => ({
338
+ role: m.role,
339
+ content: m.content,
340
+ toolCallId: m.toolCallId,
341
+ toolCalls: m.toolCalls
342
+ })));
343
+ ui.success(`Resumed session ${ui.bold(session.id.slice(0, 8))} (${session.messages.length} messages)`);
344
+ continue;
345
+ }
346
+ if (command === '/sessions') {
347
+ const list = await sessionManager.list();
348
+ ui.heading('Saved Sessions');
349
+ if (list.length === 0) {
350
+ console.log(ui.dim('No saved sessions.'));
351
+ }
352
+ else {
353
+ for (const s of list.slice(0, 8)) {
354
+ const isCur = s.id === session.id ? ui.accent('*') : ' ';
355
+ const summary = s.summary ? ` — "${s.summary}"` : '';
356
+ console.log(` ${isCur} ${ui.bold(s.id.slice(0, 8))} ${ui.dim(`[${s.model}]`)} ${ui.dim(`(${s.messageCount} msgs)`)}${summary}`);
357
+ console.log(` ${ui.dim(new Date(s.lastActiveAt).toLocaleString())} ${ui.dim(s.cwd)}`);
358
+ }
359
+ }
360
+ continue;
361
+ }
362
+ // ─────────────────────────────────────────────────────────────
363
+ // 2. PROJECT & CONTEXT COMMANDS
364
+ // ─────────────────────────────────────────────────────────────
365
+ if (command === '/init') {
366
+ await init();
367
+ systemContext = await buildContext(cwd, config);
368
+ continue;
369
+ }
370
+ if (command === '/context') {
371
+ const metrics = await inspectProjectContext(cwd);
372
+ ui.statusCard('Workspace Context Intelligence', {
373
+ 'Directory': metrics.cwd,
374
+ 'Project Stacks': metrics.projectTypes.join(', ') || 'General',
375
+ 'Git Branch / Status': `${metrics.gitBranch} (${metrics.gitStatus})`,
376
+ 'Discovered Files': `${metrics.filesDiscovered} files (${metrics.filesSample.join(', ')}...)`,
377
+ 'Instructions File': metrics.instructionsSource || 'None loaded',
378
+ 'Memory Entries': `${metrics.memoryEntriesCount} entries in .overarch/memory.md`,
379
+ 'Estimated Tokens': `~${metrics.estimatedTokens} tokens`
380
+ });
381
+ continue;
382
+ }
383
+ if (command === '/reload' || command === '/refresh') {
384
+ config = await loadConfig();
385
+ systemContext = await buildContext(cwd, config);
386
+ if (config.ui?.accentColor)
387
+ ui.setAccent(config.ui.accentColor);
388
+ if (config.ui?.avatar)
389
+ ui.setAvatar(config.ui.avatar);
390
+ ui.success('Project context, instructions, and configuration reloaded.');
391
+ continue;
392
+ }
393
+ if (command === '/project' || command.startsWith('/project ')) {
394
+ const targetDir = command.replace(/^\/project\s*/, '').trim();
395
+ if (!targetDir || targetDir === 'status') {
396
+ const metrics = await inspectProjectContext(cwd);
397
+ ui.statusCard('Active Project Information', {
398
+ 'Directory': cwd,
399
+ 'Tech Ecosystem': metrics.projectTypes.join(', ') || 'General',
400
+ 'Git Branch': metrics.gitBranch,
401
+ 'Files Discovered': metrics.filesDiscovered,
402
+ 'Instructions': metrics.instructionsSource || 'None',
403
+ 'Memory Log': `${metrics.memoryEntriesCount} items`
404
+ });
405
+ }
406
+ else if (targetDir === 'recent') {
407
+ const sessions = await sessionManager.list();
408
+ const projects = Array.from(new Set(sessions.map(s => s.cwd)));
409
+ ui.heading('Recently Accessed Projects');
410
+ projects.slice(0, 6).forEach((p, idx) => {
411
+ const isCur = p === cwd ? ui.accent(' (current)') : '';
412
+ console.log(` ${idx + 1}. ${p}${isCur}`);
413
+ });
414
+ }
415
+ else {
416
+ const resolved = path.resolve(cwd, targetDir);
417
+ try {
418
+ const stats = await fs.stat(resolved);
419
+ if (!stats.isDirectory()) {
420
+ ui.error(`Not a directory: ${resolved}`);
421
+ continue;
422
+ }
423
+ process.chdir(resolved);
424
+ cwd = resolved;
425
+ await autoEnsureOverarch(cwd);
426
+ sessionManager = new SessionManager(undefined, cwd);
427
+ checkpointManager = new CheckpointManager(cwd);
428
+ systemContext = await buildContext(cwd, config);
429
+ session = await sessionManager.create(cwd, session.model);
430
+ agentRuntime.setMessages([]);
431
+ ui.success(`Switched workspace to: ${cwd}`);
432
+ ui.info(`Fresh session created for workspace: ${session.id.slice(0, 8)}`);
433
+ }
434
+ catch (e) {
435
+ ui.error(`Cannot switch directory: ${e.message}`);
436
+ }
437
+ }
438
+ continue;
439
+ }
440
+ if (command === '/memory' || command.startsWith('/memory ')) {
441
+ const rest = command.replace(/^\/memory\s*/, '').trim();
442
+ const parts = rest.split(/\s+/);
443
+ const sub = parts[0] || 'show';
444
+ const arg1 = parts.slice(1).join(' ');
445
+ if (sub === 'search') {
446
+ await handleMemoryCommand('search', arg1);
447
+ }
448
+ else if (sub === 'forget') {
449
+ await handleMemoryCommand('forget', parts[1]);
450
+ }
451
+ else if (sub === 'show' || sub === 'list' || !rest) {
452
+ await handleMemoryCommand('show');
453
+ }
454
+ else {
455
+ // If user typed a raw note e.g. /memory Use pnpm for tests
456
+ await handleMemoryCommand('learn', rest);
457
+ }
458
+ continue;
459
+ }
460
+ if (command === '/learn' || command.startsWith('/learn ')) {
461
+ const lessonText = command.replace(/^\/learn\s*/, '').trim();
462
+ await handleLearnCommand(lessonText);
463
+ continue;
464
+ }
465
+ // ─────────────────────────────────────────────────────────────
466
+ // 3. AI, MODEL & AGENT MODE COMMANDS
467
+ // ─────────────────────────────────────────────────────────────
468
+ if (command.startsWith('/model')) {
469
+ const newModel = command.replace(/^\/model\s*/, '').trim();
470
+ if (!newModel) {
471
+ const models = listModels();
472
+ ui.heading('Available Models');
473
+ models.forEach(m => {
474
+ const isCur = m.id === session.model ? ui.accent(' (active)') : '';
475
+ console.log(` ${ui.bold(m.id)} ${ui.dim(`[${m.provider}]`)} — ${m.name}${isCur}`);
476
+ });
477
+ console.log(`\nUsage: /model <model_id> (e.g. /model claude-3-5-sonnet-20241022 or /model gpt-4o)`);
478
+ continue;
479
+ }
480
+ try {
481
+ const resolved = resolveModel(newModel);
482
+ if (!resolved) {
483
+ ui.warn(`Model '${newModel}' could not be resolved automatically. Attempting raw switch.`);
484
+ }
485
+ const modelId = resolved ? resolved.model : newModel;
486
+ session.model = modelId;
487
+ config.defaultModel = modelId;
488
+ config.mode = 'online';
489
+ providerResult = resolveRuntimeProvider(config, modelId);
490
+ agentRuntime = new AgentRuntime(providerResult.provider, registry, {
491
+ ...config,
492
+ defaultModel: providerResult.model
493
+ }, { autoApprove: args.yes || config.agentMode === 'autonomous', rl });
494
+ agentRuntime.setMessages(session.messages.map(m => ({ role: m.role, content: m.content })));
495
+ await saveGlobalConfig({ defaultModel: modelId, mode: 'online' });
496
+ ui.success(`Switched model to: ${ui.bold(providerResult.displayName)} (${providerResult.model})`);
497
+ }
498
+ catch (err) {
499
+ ui.error(`Failed to switch model: ${err.message}`);
500
+ }
501
+ continue;
502
+ }
503
+ if (command === '/plan' || command.startsWith('/plan ')) {
504
+ const arg = command.replace(/^\/plan\s*/, '').trim().toLowerCase();
505
+ if (arg === 'on') {
506
+ config.agentMode = 'plan';
507
+ ui.success('Plan Mode: ENABLED. Overarch will structure explicit roadmaps before code changes.');
508
+ }
509
+ else if (arg === 'off') {
510
+ config.agentMode = 'approval';
511
+ ui.info('Plan Mode: DISABLED (Standard approval mode).');
512
+ }
513
+ else {
514
+ const isPlan = config.agentMode === 'plan';
515
+ config.agentMode = isPlan ? 'approval' : 'plan';
516
+ ui.info(`Plan Mode toggled: ${config.agentMode === 'plan' ? ui.green('ON') : ui.dim('OFF')}`);
517
+ }
518
+ agentRuntime.setConfig(config);
519
+ continue;
520
+ }
521
+ // ─────────────────────────────────────────────────────────────
522
+ // 4. PRESERVED & ENHANCED TRIAD COMMANDS: /ponytail, /ccss-5-0, /cchcfai, /design-critique
523
+ // ─────────────────────────────────────────────────────────────
524
+ if (command === '/ponytail' || command.startsWith('/ponytail ')) {
525
+ const level = command.replace(/^\/ponytail\s*/, '').trim().toLowerCase();
526
+ if (level === 'lite' || level === 'full' || level === 'ultra') {
527
+ config.ponytail = level;
528
+ await saveGlobalConfig({ ponytail: level });
529
+ systemContext = await buildContext(cwd, config);
530
+ ui.success(`Ponytail mode set to: ${ui.bold(level.toUpperCase())}`);
531
+ console.log(ui.dim(level === 'ultra' ? 'Ultra YAGNI: shortest diff, zero extra abstractions, stdlib first.' : 'Enforcing senior dev simplicity ladder.'));
532
+ }
533
+ else {
534
+ console.log(`\n${ui.accent(ui.getAvatar())} ${ui.bold('Ponytail Mode')}: ${config.ponytail.toUpperCase()}`);
535
+ console.log('The lazy senior dev ladder: YAGNI, stdlib over deps, shortest working diff.');
536
+ console.log(`Usage: /ponytail lite | full | ultra\n`);
537
+ }
538
+ continue;
539
+ }
540
+ if (command === '/ccss' || command === '/ccss-5-0' || command.startsWith('/ccss ') || command.startsWith('/ccss-5-0 ')) {
541
+ const parsed = parseCcssCommand(command);
542
+ if (parsed.isStatus) {
543
+ const activeCfg = CCSS_MODE_CONFIGS[config.ccss || 'auto'];
544
+ ui.statusCard('CCSS 5.0 Orchestration Status', {
545
+ 'Active Mode': activeCfg.name,
546
+ 'Description': activeCfg.description,
547
+ 'Planning Depth': activeCfg.planningDepth,
548
+ 'Pre-Mutation Checkpoint': activeCfg.requireCheckpoint ? 'Enforced' : 'Optional',
549
+ 'Verification Intensity': activeCfg.verificationIntensity.toUpperCase(),
550
+ 'Parallel Inspection': activeCfg.parallelInspection ? 'Enabled' : 'Disabled',
551
+ 'Approval Gate': activeCfg.approvalGate
552
+ });
553
+ continue;
554
+ }
555
+ if (parsed.mode) {
556
+ config.ccss = parsed.mode;
557
+ await saveGlobalConfig({ ccss: parsed.mode });
558
+ agentRuntime.setConfig(config);
559
+ systemContext = await buildContext(cwd, config);
560
+ ui.success(`CCSS 5.0 mode set to: ${ui.bold(parsed.mode.toUpperCase())}`);
561
+ if (parsed.prompt) {
562
+ ui.userMessage(parsed.prompt);
563
+ await runAgent(parsed.prompt);
564
+ }
565
+ continue;
566
+ }
567
+ if (parsed.prompt) {
568
+ await runAgent(parsed.prompt);
569
+ continue;
570
+ }
571
+ console.log(`\n${ui.accent(ui.getAvatar())} ${ui.bold('CCSS 5.0 Mission Orchestration')}: ${config.ccss.toUpperCase()}`);
572
+ console.log('Orchestrate complex tasks with capability discovery, verification checkpoints, and recovery.');
573
+ console.log(`Usage: /ccss auto | master | god | control [prompt]\n`);
574
+ continue;
575
+ }
576
+ if (command === '/checkpoint' || command.startsWith('/checkpoint ')) {
577
+ const name = command.replace(/^\/checkpoint\s*/, '').trim() || 'manual';
578
+ try {
579
+ const info = await checkpointManager.create(name);
580
+ ui.success(`Created checkpoint: ${ui.bold(info.id)} (${info.name}) [${info.type}]`);
581
+ }
582
+ catch (err) {
583
+ ui.error(`Failed to create checkpoint: ${err.message}`);
584
+ }
585
+ continue;
586
+ }
587
+ if (command === '/checkpoints') {
588
+ const list = await checkpointManager.list();
589
+ ui.heading('Saved Checkpoints');
590
+ if (list.length === 0) {
591
+ console.log(ui.dim('No checkpoints saved yet. Create one using: /checkpoint [name]'));
592
+ }
593
+ else {
594
+ list.slice(0, 10).forEach((c, idx) => {
595
+ console.log(` ${idx + 1}. ${ui.bold(c.id)} — ${c.name} ${ui.dim(`(${c.type} at ${new Date(c.timestamp).toLocaleTimeString()})`)}`);
596
+ });
597
+ }
598
+ continue;
599
+ }
600
+ if (command === '/rollback' || command.startsWith('/rollback ')) {
601
+ const targetId = command.replace(/^\/rollback\s*/, '').trim() || undefined;
602
+ const res = await checkpointManager.rollback(targetId);
603
+ if (res.success) {
604
+ ui.success(`Rolled back successfully to checkpoint ${targetId || 'latest'}.`);
605
+ if (res.output)
606
+ console.log(ui.dim(res.output));
607
+ }
608
+ else {
609
+ ui.fail(`Rollback failed: ${res.error}`);
610
+ }
611
+ continue;
612
+ }
613
+ if (command === '/diff' || command === '/changes') {
614
+ try {
615
+ const { stdout: diffOut } = await execAsync('git diff HEAD', { cwd });
616
+ if (!diffOut.trim()) {
617
+ ui.info('Working tree is clean (no uncommitted diffs).');
618
+ }
619
+ else {
620
+ const formatted = ui.formatDiff(diffOut);
621
+ console.log(formatted || diffOut);
622
+ }
623
+ }
624
+ catch {
625
+ ui.warn('Git diff unavailable in current directory.');
626
+ }
627
+ continue;
628
+ }
629
+ if (command === '/retry') {
630
+ const userMsgs = session.messages.filter(m => m.role === 'user');
631
+ const lastMsg = userMsgs[userMsgs.length - 1];
632
+ if (!lastMsg) {
633
+ ui.warn('No previous prompt to retry.');
634
+ continue;
635
+ }
636
+ ui.info(`Retrying last prompt: "${ui.bold(lastMsg.content)}"`);
637
+ await runAgent(lastMsg.content);
638
+ continue;
639
+ }
640
+ if (command === '/cchcfai' || command.startsWith('/cchcfai ')) {
641
+ const action = command.replace(/^\/cchcfai\s*/, '').trim().toLowerCase();
642
+ if (action === 'gate') {
643
+ config.agentMode = 'approval';
644
+ ui.success('CCHCFAI Gate: Human approval required for all state-mutating actions.');
645
+ }
646
+ else if (action === 'halt' || action === 'stop') {
647
+ agentRuntime.abort();
648
+ ui.warn('CCHCFAI Emergency Stop: Agent paused and yielded control.');
649
+ }
650
+ else if (action === 'audit') {
651
+ ui.statusCard('CCHCFAI Human Control Audit', {
652
+ 'Autonomy Mode': config.agentMode.toUpperCase(),
653
+ 'Auto-Approved': config.permissions.autoApprove.join(', '),
654
+ 'Requires Approval': config.permissions.requireApproval.join(', '),
655
+ 'Denied Tools': config.permissions.deny.join(', ') || 'none',
656
+ 'Emergency Stop': 'Active (Ctrl+C or /halt)'
657
+ });
658
+ }
659
+ else {
660
+ console.log(`\n${ui.accent(ui.getAvatar())} ${ui.bold('CCHCFAI Human Control Architecture')}`);
661
+ console.log('Autonomy is bounded, observable, interruptible, and subordinate to the human.');
662
+ console.log(`Usage: /cchcfai gate | audit | halt | status\n`);
663
+ }
664
+ continue;
665
+ }
666
+ if (command === '/design-critique' || command.startsWith('/design-critique ')) {
667
+ ui.heading('Design & UX Review Mode');
668
+ console.log('Overarch evaluates visual hierarchy, layout, typography, and accessibility.');
669
+ console.log('Enter prompt describing UI component, terminal output, or design spec to critique.\n');
670
+ continue;
671
+ }
672
+ // ─────────────────────────────────────────────────────────────
673
+ // 5. FLOATING DESKTOP COMPANION: /companion, /petdex [start|stop|pets|select|install|status]
674
+ // ─────────────────────────────────────────────────────────────
675
+ if (command === '/companion' || command.startsWith('/companion ') || command === '/petdex' || command.startsWith('/petdex ')) {
676
+ const isPetdex = command.startsWith('/petdex');
677
+ const prefix = isPetdex ? /^\/petdex\s*/ : /^\/companion\s*/;
678
+ const rest = command.replace(prefix, '').trim();
679
+ const parts = rest.split(/\s+/);
680
+ const sub = parts[0] || 'status';
681
+ const arg = parts.slice(1).join(' ');
682
+ await handleCompanionCommand(sub, arg);
683
+ continue;
684
+ }
685
+ // ─────────────────────────────────────────────────────────────
686
+ // 6. SYSTEM & DIAGNOSTIC COMMANDS: /doctor, /status, /config, /setup, /tools
687
+ // ─────────────────────────────────────────────────────────────
688
+ if (command === '/doctor') {
689
+ await doctor();
690
+ continue;
691
+ }
692
+ if (command === '/status') {
693
+ await status();
694
+ continue;
695
+ }
696
+ if (command.startsWith('/config')) {
697
+ const rawParts = command.replace(/^\/config\s*/, '').trim().split(/\s+/);
698
+ const sub = rawParts[0] || 'show';
699
+ const key = rawParts[1];
700
+ const val = rawParts.slice(2).join(' ');
701
+ await handleConfigCommand(sub, key, val);
702
+ continue;
703
+ }
704
+ if (command === '/setup') {
705
+ await runSetup(rl);
706
+ config = await loadConfig();
707
+ providerResult = resolveRuntimeProvider(config);
708
+ agentRuntime = new AgentRuntime(providerResult.provider, registry, {
709
+ ...config,
710
+ defaultModel: providerResult.model
711
+ }, { autoApprove: args.yes || config.agentMode === 'autonomous', rl });
712
+ systemContext = await buildContext(cwd, config);
713
+ ui.success('Overarch configuration updated and reloaded.');
714
+ continue;
715
+ }
716
+ if (command === '/tools') {
717
+ const tools = registry.list();
718
+ ui.heading('Registered Tools & MCP Connectors');
719
+ for (const t of tools) {
720
+ const isMcp = t.name.startsWith('mcp_') ? ui.accent('[MCP] ') : '';
721
+ console.log(` ${ui.accent('▸')} ${isMcp}${ui.bold(t.name)} ${ui.dim(`[${t.tier}]`)} — ${t.description}`);
722
+ }
723
+ continue;
724
+ }
725
+ if (command === '/compact') {
726
+ args.compact = !args.compact;
727
+ ui.info(`Compact terminal mode: ${args.compact ? 'enabled' : 'disabled'}`);
728
+ continue;
729
+ }
730
+ if (command === '/abort' || command === '/halt') {
731
+ agentRuntime.abort();
732
+ ui.info('Aborted current action.');
733
+ continue;
734
+ }
735
+ // ─────────────────────────────────────────────────────────────
736
+ // 7. CATEGORIZED /HELP & CONTEXTUAL HELP
737
+ // ─────────────────────────────────────────────────────────────
738
+ if (command === '/help' || command.startsWith('/help ')) {
739
+ const topic = command.replace(/^\/help\s*/, '').trim().toLowerCase();
740
+ if (topic === 'petdex') {
741
+ console.log(`\n${ui.bold('Help: Petdex Integration')}\nOptional bridge to external Petdex desktop companion app via local event server.\nCommands: /petdex status, /petdex launch, /petdex pets, /petdex select <id>`);
742
+ }
743
+ else if (topic === 'model') {
744
+ console.log(`\n${ui.bold('Help: Model Switching')}\nSwitch active models mid-session: /model <name>\nExamples: /model claude-3-5-sonnet-20241022, /model gpt-4o, /model gemini-2.5-flash`);
745
+ }
746
+ else if (topic === 'project') {
747
+ console.log(`\n${ui.bold('Help: Project Management')}\nManage repository workspace context: /project [dir], /project recent, /init, /context, /reload, /memory`);
748
+ }
749
+ else if (topic === 'ponytail') {
750
+ console.log(`\n${ui.bold('Help: Ponytail Ladder')}\nEnforce senior dev minimalism: /ponytail lite | full | ultra\nYAGNI, stdlib before custom code, shortest working diff.`);
751
+ }
752
+ else if (topic === 'ccss') {
753
+ console.log(`\n${ui.bold('Help: CCSS 5.0')}\nMission orchestration: /ccss auto | master | god | control\nDecomposes tasks into verified checkpoints.`);
754
+ }
755
+ else if (topic === 'cchcfai') {
756
+ console.log(`\n${ui.bold('Help: CCHCFAI')}\nHuman control framework: /cchcfai gate | audit | halt | status\nEnforces 4-state separation and confirmation gates.`);
757
+ }
758
+ else {
759
+ console.log(`
760
+ ${ui.bold('SESSION')}
761
+ /status Display diagnostic dashboard card
762
+ /new Start a fresh session in current project
763
+ /resume [id] Resume past session by ID or prefix
764
+ /sessions List saved sessions with summaries
765
+ /clear Clear conversation history
766
+ /exit, /quit Save session and exit cleanly
767
+
768
+ ${ui.bold('PROJECT & REPOSITORY')}
769
+ /project [dir] Inspect current workspace or switch directory
770
+ /project recent List recently accessed projects
771
+ /init Initialize .overarch subsystem for this project
772
+ /context Deep inspection of files, stack, and tokens
773
+ /reload Re-scan workspace context and instructions
774
+ /memory [note] View or append notes in .overarch/memory.md
775
+ /checkpoint [name] Create an instant git/snapshot checkpoint
776
+ /checkpoints List all saved checkpoints
777
+ /rollback [id] Revert repository to a previous checkpoint
778
+ /diff, /changes Inspect uncommitted git working tree changes
779
+
780
+ ${ui.bold('AI & AGENT')}
781
+ /model [name] List available models or switch active model
782
+ /plan [on|off] Toggle Plan Mode (roadmap before changes)
783
+ /retry Re-execute last prompt with updated approach
784
+ /ponytail [level] Set Ponytail minimalism: lite | full | ultra
785
+ /ccss [mode] Set CCSS mission orchestration: auto | master | god | control
786
+ /cchcfai [action] Human control layer: gate | audit | halt | status
787
+ /design-critique Design review mode for UI, layout, and a11y
788
+ /tools List available tools, permission tiers, and MCP
789
+
790
+ ${ui.bold('PERSISTENT LEARNING & MEMORY')}
791
+ /memory [show] Display project memory & learned episodic lessons
792
+ /memory search <q> Search lessons, conventions, and architectural decisions
793
+ /memory forget <id> Remove a specific learned memory
794
+ /learn "<lesson>" Record a new engineering insight into memory
795
+
796
+ ${ui.bold('FLOATING DESKTOP COMPANION (PETDEX)')}
797
+ /companion start Launch floating transparent desktop companion
798
+ /companion stop Close desktop companion
799
+ /companion status Display companion window state and active pet
800
+ /companion pets List all installed Petdex companions
801
+ /companion select Switch active desktop companion
802
+ /companion install Install any pet from Petdex (e.g. /companion install savage-codex-hacker)
803
+
804
+ ${ui.bold('SYSTEM')}
805
+ /doctor Run full health and diagnostic checks
806
+ /config [target] Direct configuration (provider, model, accent, permissions)
807
+ /setup Reopen guided setup wizard
808
+ /compact Toggle compact terminal output
809
+ /abort, /halt Emergency stop current agent operation
810
+ `);
811
+ }
812
+ continue;
813
+ }
814
+ if (command.startsWith('/')) {
815
+ ui.warn(`Unknown command: ${command}. Type /help for available commands.`);
816
+ continue;
817
+ }
818
+ await runAgent(command);
819
+ }
820
+ rl.close();
821
+ }