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.
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/dist/bin/overarch.d.ts +2 -0
- package/dist/bin/overarch.js +178 -0
- package/dist/src/agent/context.d.ts +14 -0
- package/dist/src/agent/context.js +200 -0
- package/dist/src/agent/context_compressor.d.ts +21 -0
- package/dist/src/agent/context_compressor.js +90 -0
- package/dist/src/agent/manifest.d.ts +22 -0
- package/dist/src/agent/manifest.js +131 -0
- package/dist/src/agent/planner.d.ts +38 -0
- package/dist/src/agent/planner.js +118 -0
- package/dist/src/agent/runtime.d.ts +59 -0
- package/dist/src/agent/runtime.js +400 -0
- package/dist/src/cli/approval.d.ts +13 -0
- package/dist/src/cli/approval.js +71 -0
- package/dist/src/cli/args.d.ts +15 -0
- package/dist/src/cli/args.js +47 -0
- package/dist/src/cli/commands/audit.d.ts +10 -0
- package/dist/src/cli/commands/audit.js +79 -0
- package/dist/src/cli/commands/companion.d.ts +1 -0
- package/dist/src/cli/commands/companion.js +143 -0
- package/dist/src/cli/commands/config.d.ts +1 -0
- package/dist/src/cli/commands/config.js +135 -0
- package/dist/src/cli/commands/doctor.d.ts +1 -0
- package/dist/src/cli/commands/doctor.js +217 -0
- package/dist/src/cli/commands/init.d.ts +1 -0
- package/dist/src/cli/commands/init.js +134 -0
- package/dist/src/cli/commands/memory.d.ts +2 -0
- package/dist/src/cli/commands/memory.js +98 -0
- package/dist/src/cli/commands/onboarding.d.ts +3 -0
- package/dist/src/cli/commands/onboarding.js +223 -0
- package/dist/src/cli/commands/sessions.d.ts +1 -0
- package/dist/src/cli/commands/sessions.js +22 -0
- package/dist/src/cli/commands/status.d.ts +1 -0
- package/dist/src/cli/commands/status.js +37 -0
- package/dist/src/cli/commands/update.d.ts +9 -0
- package/dist/src/cli/commands/update.js +105 -0
- package/dist/src/cli/repl.d.ts +3 -0
- package/dist/src/cli/repl.js +821 -0
- package/dist/src/cli/ui.d.ts +53 -0
- package/dist/src/cli/ui.js +215 -0
- package/dist/src/companion/petdex_adapter.d.ts +31 -0
- package/dist/src/companion/petdex_adapter.js +171 -0
- package/dist/src/companion/petdex_bridge.d.ts +65 -0
- package/dist/src/companion/petdex_bridge.js +356 -0
- package/dist/src/companion/types.d.ts +57 -0
- package/dist/src/companion/types.js +11 -0
- package/dist/src/config/index.d.ts +24 -0
- package/dist/src/config/index.js +237 -0
- package/dist/src/config/types.d.ts +58 -0
- package/dist/src/config/types.js +7 -0
- package/dist/src/core/diagnostics.d.ts +48 -0
- package/dist/src/core/diagnostics.js +115 -0
- package/dist/src/core/index.d.ts +23 -0
- package/dist/src/core/index.js +23 -0
- package/dist/src/core/mission.d.ts +55 -0
- package/dist/src/core/mission.js +173 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/learning/memory_manager.d.ts +26 -0
- package/dist/src/learning/memory_manager.js +211 -0
- package/dist/src/learning/types.d.ts +54 -0
- package/dist/src/learning/types.js +1 -0
- package/dist/src/mcp/registry.d.ts +17 -0
- package/dist/src/mcp/registry.js +97 -0
- package/dist/src/mcp/types.d.ts +13 -0
- package/dist/src/mcp/types.js +1 -0
- package/dist/src/providers/anthropic.d.ts +10 -0
- package/dist/src/providers/anthropic.js +95 -0
- package/dist/src/providers/base.d.ts +15 -0
- package/dist/src/providers/base.js +108 -0
- package/dist/src/providers/factory.d.ts +28 -0
- package/dist/src/providers/factory.js +142 -0
- package/dist/src/providers/gemini.d.ts +10 -0
- package/dist/src/providers/gemini.js +97 -0
- package/dist/src/providers/openai.d.ts +9 -0
- package/dist/src/providers/openai.js +85 -0
- package/dist/src/providers/openrouter.d.ts +5 -0
- package/dist/src/providers/openrouter.js +14 -0
- package/dist/src/providers/registry.d.ts +19 -0
- package/dist/src/providers/registry.js +209 -0
- package/dist/src/providers/types.d.ts +59 -0
- package/dist/src/providers/types.js +1 -0
- package/dist/src/providers/validator.d.ts +24 -0
- package/dist/src/providers/validator.js +161 -0
- package/dist/src/security/audit.d.ts +56 -0
- package/dist/src/security/audit.js +154 -0
- package/dist/src/security/guardrails.d.ts +9 -0
- package/dist/src/security/guardrails.js +32 -0
- package/dist/src/security/permissions.d.ts +12 -0
- package/dist/src/security/permissions.js +68 -0
- package/dist/src/security/policy.d.ts +220 -0
- package/dist/src/security/policy.js +720 -0
- package/dist/src/session/manager.d.ts +22 -0
- package/dist/src/session/manager.js +172 -0
- package/dist/src/session/types.d.ts +36 -0
- package/dist/src/session/types.js +1 -0
- package/dist/src/tools/checkpoint.d.ts +26 -0
- package/dist/src/tools/checkpoint.js +160 -0
- package/dist/src/tools/diagnostics.d.ts +2 -0
- package/dist/src/tools/diagnostics.js +125 -0
- package/dist/src/tools/diff.d.ts +14 -0
- package/dist/src/tools/diff.js +124 -0
- package/dist/src/tools/filesystem.d.ts +2 -0
- package/dist/src/tools/filesystem.js +224 -0
- package/dist/src/tools/git.d.ts +2 -0
- package/dist/src/tools/git.js +138 -0
- package/dist/src/tools/markdown.d.ts +33 -0
- package/dist/src/tools/markdown.js +190 -0
- package/dist/src/tools/project.d.ts +2 -0
- package/dist/src/tools/project.js +63 -0
- package/dist/src/tools/registry.d.ts +14 -0
- package/dist/src/tools/registry.js +230 -0
- package/dist/src/tools/search.d.ts +2 -0
- package/dist/src/tools/search.js +136 -0
- package/dist/src/tools/shell.d.ts +2 -0
- package/dist/src/tools/shell.js +74 -0
- package/dist/src/tools/symbols.d.ts +30 -0
- package/dist/src/tools/symbols.js +155 -0
- package/dist/src/tools/types.d.ts +28 -0
- package/dist/src/tools/types.js +1 -0
- package/dist/src/tools/web_search.d.ts +14 -0
- package/dist/src/tools/web_search.js +154 -0
- package/install.sh +29 -0
- package/overarch +15 -0
- package/package.json +28 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import { styleText } from 'node:util';
|
|
3
|
+
/**
|
|
4
|
+
* Parses markdown front matter (YAML-like key: value between --- fences)
|
|
5
|
+
*/
|
|
6
|
+
export function parseFrontMatter(content) {
|
|
7
|
+
if (!content.startsWith('---')) {
|
|
8
|
+
return { body: content };
|
|
9
|
+
}
|
|
10
|
+
const endIdx = content.indexOf('\n---', 3);
|
|
11
|
+
if (endIdx === -1) {
|
|
12
|
+
return { body: content };
|
|
13
|
+
}
|
|
14
|
+
const fmLines = content.slice(3, endIdx).trim().split('\n');
|
|
15
|
+
const frontMatter = {};
|
|
16
|
+
for (const line of fmLines) {
|
|
17
|
+
const colon = line.indexOf(':');
|
|
18
|
+
if (colon > 0) {
|
|
19
|
+
const key = line.slice(0, colon).trim();
|
|
20
|
+
const val = line.slice(colon + 1).trim();
|
|
21
|
+
frontMatter[key] = val;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const body = content.slice(endIdx + 4).trimStart();
|
|
25
|
+
return { frontMatter, body };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parses markdown structural elements (headings, code blocks, tables)
|
|
29
|
+
*/
|
|
30
|
+
export function inspectMarkdown(content) {
|
|
31
|
+
const { frontMatter } = parseFrontMatter(content);
|
|
32
|
+
const lines = content.split('\n');
|
|
33
|
+
const headings = [];
|
|
34
|
+
let inCodeBlock = false;
|
|
35
|
+
let codeBlocksCount = 0;
|
|
36
|
+
let tablesCount = 0;
|
|
37
|
+
let inTable = false;
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
const line = lines[i];
|
|
40
|
+
if (line.trim().startsWith('```')) {
|
|
41
|
+
inCodeBlock = !inCodeBlock;
|
|
42
|
+
if (inCodeBlock)
|
|
43
|
+
codeBlocksCount++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!inCodeBlock) {
|
|
47
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.*)$/);
|
|
48
|
+
if (headingMatch) {
|
|
49
|
+
headings.push({
|
|
50
|
+
level: headingMatch[1].length,
|
|
51
|
+
text: headingMatch[2].trim(),
|
|
52
|
+
line: i + 1
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
|
|
56
|
+
if (!inTable) {
|
|
57
|
+
tablesCount++;
|
|
58
|
+
inTable = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
inTable = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
frontMatter,
|
|
68
|
+
headings,
|
|
69
|
+
codeBlocksCount,
|
|
70
|
+
tablesCount,
|
|
71
|
+
rawContent: content
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Non-destructive markdown section patcher.
|
|
76
|
+
* Replaces ONLY the target section under a specific heading without corrupting surrounding text.
|
|
77
|
+
*/
|
|
78
|
+
export async function patchMarkdownSection(filePath, targetHeading, newSectionContent) {
|
|
79
|
+
let content;
|
|
80
|
+
try {
|
|
81
|
+
content = await fs.readFile(filePath, 'utf8');
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return { success: false, output: '', error: `Cannot read markdown file: ${err.message}` };
|
|
85
|
+
}
|
|
86
|
+
const lines = content.split('\n');
|
|
87
|
+
const targetClean = targetHeading.replace(/^#+\s*/, '').trim().toLowerCase();
|
|
88
|
+
let targetIndex = -1;
|
|
89
|
+
let targetLevel = 1;
|
|
90
|
+
let inCodeBlock = false;
|
|
91
|
+
for (let i = 0; i < lines.length; i++) {
|
|
92
|
+
const line = lines[i];
|
|
93
|
+
if (line.trim().startsWith('```')) {
|
|
94
|
+
inCodeBlock = !inCodeBlock;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!inCodeBlock) {
|
|
98
|
+
const match = line.match(/^(#{1,6})\s+(.*)$/);
|
|
99
|
+
if (match) {
|
|
100
|
+
const text = match[2].trim().toLowerCase();
|
|
101
|
+
if (text === targetClean) {
|
|
102
|
+
targetIndex = i;
|
|
103
|
+
targetLevel = match[1].length;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (targetIndex === -1) {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
output: '',
|
|
113
|
+
error: `Heading '${targetHeading}' not found in ${filePath}`
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Find where this section ends (next heading of <= targetLevel, or EOF)
|
|
117
|
+
let nextSectionIndex = lines.length;
|
|
118
|
+
inCodeBlock = false;
|
|
119
|
+
for (let i = targetIndex + 1; i < lines.length; i++) {
|
|
120
|
+
const line = lines[i];
|
|
121
|
+
if (line.trim().startsWith('```')) {
|
|
122
|
+
inCodeBlock = !inCodeBlock;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!inCodeBlock) {
|
|
126
|
+
const match = line.match(/^(#{1,6})\s+(.*)$/);
|
|
127
|
+
if (match && match[1].length <= targetLevel) {
|
|
128
|
+
nextSectionIndex = i;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Preserve the heading line itself, replace lines between targetIndex + 1 and nextSectionIndex
|
|
134
|
+
const headingLine = lines[targetIndex];
|
|
135
|
+
const before = lines.slice(0, targetIndex + 1);
|
|
136
|
+
const after = lines.slice(nextSectionIndex);
|
|
137
|
+
const cleanNewContent = newSectionContent.trim();
|
|
138
|
+
const assembled = [...before, cleanNewContent, '', ...after].join('\n');
|
|
139
|
+
try {
|
|
140
|
+
await fs.writeFile(filePath, assembled, 'utf8');
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
output: `Successfully updated section '${headingLine}' in ${filePath}`,
|
|
144
|
+
metadata: { targetHeading, modifiedLines: nextSectionIndex - targetIndex }
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
return { success: false, output: '', error: `Failed to write file: ${err.message}` };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Terminal syntax renderer for markdown text
|
|
153
|
+
*/
|
|
154
|
+
export function renderMarkdown(content) {
|
|
155
|
+
const lines = content.split('\n');
|
|
156
|
+
const rendered = [];
|
|
157
|
+
let inCodeBlock = false;
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
if (line.trim().startsWith('```')) {
|
|
160
|
+
inCodeBlock = !inCodeBlock;
|
|
161
|
+
rendered.push(styleText('dim', line));
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (inCodeBlock) {
|
|
165
|
+
rendered.push(styleText('cyan', ` ${line}`));
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const h1 = line.match(/^#\s+(.*)/);
|
|
169
|
+
if (h1) {
|
|
170
|
+
rendered.push(`\n${styleText(['bold', 'underline'], h1[1])}\n`);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const h2 = line.match(/^##\s+(.*)/);
|
|
174
|
+
if (h2) {
|
|
175
|
+
rendered.push(`\n${styleText(['bold', 'cyan'], h2[1])}`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const h3 = line.match(/^###\s+(.*)/);
|
|
179
|
+
if (h3) {
|
|
180
|
+
rendered.push(`${styleText('bold', h3[1])}`);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (line.trim().startsWith('- ') || line.trim().startsWith('* ')) {
|
|
184
|
+
rendered.push(` ${styleText('cyan', '•')} ${line.trim().slice(2)}`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
rendered.push(line);
|
|
188
|
+
}
|
|
189
|
+
return rendered.join('\n');
|
|
190
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { PermissionTier } from '../config/types.js';
|
|
4
|
+
export function createProjectTools() {
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
definition: {
|
|
8
|
+
name: 'inspectProject',
|
|
9
|
+
description: 'Inspect project type and details',
|
|
10
|
+
tier: PermissionTier.ReadOnly,
|
|
11
|
+
parameters: []
|
|
12
|
+
},
|
|
13
|
+
handler: async () => {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const info = { type: 'unknown' };
|
|
16
|
+
try {
|
|
17
|
+
const pkgJson = await fs.readFile(path.join(cwd, 'package.json'), 'utf8');
|
|
18
|
+
const pkg = JSON.parse(pkgJson);
|
|
19
|
+
info.type = 'Node.js';
|
|
20
|
+
info.name = pkg.name;
|
|
21
|
+
info.scripts = pkg.scripts || {};
|
|
22
|
+
info.dependencies = Object.keys(pkg.dependencies || {});
|
|
23
|
+
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
24
|
+
if (allDeps['react'])
|
|
25
|
+
info.framework = 'React';
|
|
26
|
+
if (allDeps['next'])
|
|
27
|
+
info.framework = 'Next.js';
|
|
28
|
+
if (allDeps['vue'])
|
|
29
|
+
info.framework = 'Vue';
|
|
30
|
+
if (allDeps['express'])
|
|
31
|
+
info.framework = 'Express';
|
|
32
|
+
return { success: true, output: JSON.stringify(info, null, 2) };
|
|
33
|
+
}
|
|
34
|
+
catch (e) { }
|
|
35
|
+
try {
|
|
36
|
+
await fs.readFile(path.join(cwd, 'Cargo.toml'));
|
|
37
|
+
info.type = 'Rust';
|
|
38
|
+
return { success: true, output: JSON.stringify(info, null, 2) };
|
|
39
|
+
}
|
|
40
|
+
catch (e) { }
|
|
41
|
+
try {
|
|
42
|
+
await fs.readFile(path.join(cwd, 'pyproject.toml'));
|
|
43
|
+
info.type = 'Python';
|
|
44
|
+
return { success: true, output: JSON.stringify(info, null, 2) };
|
|
45
|
+
}
|
|
46
|
+
catch (e) { }
|
|
47
|
+
try {
|
|
48
|
+
await fs.readFile(path.join(cwd, 'requirements.txt'));
|
|
49
|
+
info.type = 'Python';
|
|
50
|
+
return { success: true, output: JSON.stringify(info, null, 2) };
|
|
51
|
+
}
|
|
52
|
+
catch (e) { }
|
|
53
|
+
try {
|
|
54
|
+
await fs.readFile(path.join(cwd, 'go.mod'));
|
|
55
|
+
info.type = 'Go';
|
|
56
|
+
return { success: true, output: JSON.stringify(info, null, 2) };
|
|
57
|
+
}
|
|
58
|
+
catch (e) { }
|
|
59
|
+
return { success: true, output: JSON.stringify(info, null, 2) };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Tool, ToolDefinition, ToolResult } from './types.js';
|
|
2
|
+
export declare function createWebSearchTools(): Tool[];
|
|
3
|
+
export declare function createMarkdownTools(): Tool[];
|
|
4
|
+
export declare function createSymbolAndImpactTools(): Tool[];
|
|
5
|
+
export declare function createCheckpointTools(): Tool[];
|
|
6
|
+
export declare class ToolRegistry {
|
|
7
|
+
private tools;
|
|
8
|
+
register(tool: Tool): void;
|
|
9
|
+
get(name: string): Tool | undefined;
|
|
10
|
+
list(): ToolDefinition[];
|
|
11
|
+
execute(name: string, params: Record<string, unknown>): Promise<ToolResult>;
|
|
12
|
+
toOpenAITools(): any[];
|
|
13
|
+
static createDefault(): ToolRegistry;
|
|
14
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import { PermissionTier } from '../config/types.js';
|
|
3
|
+
import { createFilesystemTools } from './filesystem.js';
|
|
4
|
+
import { createSearchTools } from './search.js';
|
|
5
|
+
import { createShellTools } from './shell.js';
|
|
6
|
+
import { createGitTools } from './git.js';
|
|
7
|
+
import { createProjectTools } from './project.js';
|
|
8
|
+
import { createDiagnosticTools } from './diagnostics.js';
|
|
9
|
+
import { inspectMarkdown, patchMarkdownSection } from './markdown.js';
|
|
10
|
+
import { analyzeChangeImpact } from './symbols.js';
|
|
11
|
+
import { CheckpointManager } from './checkpoint.js';
|
|
12
|
+
import { mcpRegistry } from '../mcp/registry.js';
|
|
13
|
+
import { searchWeb } from './web_search.js';
|
|
14
|
+
export function createWebSearchTools() {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
definition: {
|
|
18
|
+
name: 'web_search',
|
|
19
|
+
description: 'Performs an online web search for technical documentation, error resolutions, APIs, or libraries without external dependencies.',
|
|
20
|
+
parameters: [
|
|
21
|
+
{ name: 'query', type: 'string', description: 'The search query to look up on the web', required: true },
|
|
22
|
+
{ name: 'maxResults', type: 'number', description: 'Maximum number of search results to return (default: 5)', required: false }
|
|
23
|
+
],
|
|
24
|
+
tier: PermissionTier.ReadOnly
|
|
25
|
+
},
|
|
26
|
+
handler: async ({ query, maxResults }) => {
|
|
27
|
+
try {
|
|
28
|
+
const res = await searchWeb(String(query), maxResults ? Number(maxResults) : 5);
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
output: JSON.stringify(res, null, 2)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
output: '',
|
|
38
|
+
error: `Web search failed: ${err.message}`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
export function createMarkdownTools() {
|
|
46
|
+
return [
|
|
47
|
+
{
|
|
48
|
+
definition: {
|
|
49
|
+
name: 'markdown_inspect',
|
|
50
|
+
description: 'Parses a markdown file and returns its front matter, section heading outline, code blocks count, and tables.',
|
|
51
|
+
parameters: [
|
|
52
|
+
{ name: 'filePath', type: 'string', description: 'Path to the markdown file to inspect', required: true }
|
|
53
|
+
],
|
|
54
|
+
tier: PermissionTier.ReadOnly
|
|
55
|
+
},
|
|
56
|
+
handler: async ({ filePath }) => {
|
|
57
|
+
try {
|
|
58
|
+
const content = await fs.readFile(String(filePath), 'utf8');
|
|
59
|
+
const structure = inspectMarkdown(content);
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
output: JSON.stringify({
|
|
63
|
+
frontMatter: structure.frontMatter,
|
|
64
|
+
headings: structure.headings,
|
|
65
|
+
codeBlocksCount: structure.codeBlocksCount,
|
|
66
|
+
tablesCount: structure.tablesCount
|
|
67
|
+
}, null, 2)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
return { success: false, output: '', error: err.message };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
definition: {
|
|
77
|
+
name: 'markdown_patch_section',
|
|
78
|
+
description: 'Surgically replaces only the content under a specific heading in a markdown document, preserving all surrounding headings, formatting, and tables.',
|
|
79
|
+
parameters: [
|
|
80
|
+
{ name: 'filePath', type: 'string', description: 'Path to markdown file', required: true },
|
|
81
|
+
{ name: 'heading', type: 'string', description: 'Target heading to update (e.g. "Installation" or "## Overview")', required: true },
|
|
82
|
+
{ name: 'newContent', type: 'string', description: 'New content for this section', required: true }
|
|
83
|
+
],
|
|
84
|
+
tier: PermissionTier.SafeMutation
|
|
85
|
+
},
|
|
86
|
+
handler: async ({ filePath, heading, newContent }) => {
|
|
87
|
+
return await patchMarkdownSection(String(filePath), String(heading), String(newContent));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
export function createSymbolAndImpactTools() {
|
|
93
|
+
return [
|
|
94
|
+
{
|
|
95
|
+
definition: {
|
|
96
|
+
name: 'analyze_change_impact',
|
|
97
|
+
description: 'Analyzes the blast radius of changing a file: extracts its symbols, finds referencing files, and discovers relevant test files.',
|
|
98
|
+
parameters: [
|
|
99
|
+
{ name: 'filePath', type: 'string', description: 'Relative path to the file to analyze', required: true }
|
|
100
|
+
],
|
|
101
|
+
tier: PermissionTier.ReadOnly
|
|
102
|
+
},
|
|
103
|
+
handler: async ({ filePath }) => {
|
|
104
|
+
const impact = await analyzeChangeImpact(process.cwd(), String(filePath));
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
output: JSON.stringify(impact, null, 2)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
export function createCheckpointTools() {
|
|
114
|
+
const chk = new CheckpointManager();
|
|
115
|
+
return [
|
|
116
|
+
{
|
|
117
|
+
definition: {
|
|
118
|
+
name: 'create_checkpoint',
|
|
119
|
+
description: 'Creates a safe recovery checkpoint of current workspace state before risky modifications.',
|
|
120
|
+
parameters: [
|
|
121
|
+
{ name: 'name', type: 'string', description: 'Short label for the checkpoint', required: false }
|
|
122
|
+
],
|
|
123
|
+
tier: PermissionTier.SafeMutation
|
|
124
|
+
},
|
|
125
|
+
handler: async ({ name }) => {
|
|
126
|
+
const info = await chk.create(name ? String(name) : 'checkpoint');
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
output: `Checkpoint created: ${info.name} (${info.id})`
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
definition: {
|
|
135
|
+
name: 'rollback_checkpoint',
|
|
136
|
+
description: 'Reverts workspace to a previous checkpoint.',
|
|
137
|
+
parameters: [
|
|
138
|
+
{ name: 'checkpointId', type: 'string', description: 'Optional ID of checkpoint to revert to (defaults to latest)', required: false }
|
|
139
|
+
],
|
|
140
|
+
tier: PermissionTier.StateMutating
|
|
141
|
+
},
|
|
142
|
+
handler: async ({ checkpointId }) => {
|
|
143
|
+
return await chk.rollback(checkpointId ? String(checkpointId) : undefined);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
export class ToolRegistry {
|
|
149
|
+
tools = new Map();
|
|
150
|
+
register(tool) {
|
|
151
|
+
this.tools.set(tool.definition.name, tool);
|
|
152
|
+
}
|
|
153
|
+
get(name) {
|
|
154
|
+
return this.tools.get(name);
|
|
155
|
+
}
|
|
156
|
+
list() {
|
|
157
|
+
return Array.from(this.tools.values()).map(tool => tool.definition);
|
|
158
|
+
}
|
|
159
|
+
async execute(name, params) {
|
|
160
|
+
const tool = this.get(name);
|
|
161
|
+
if (!tool) {
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
output: '',
|
|
165
|
+
error: `Tool not found: ${name}`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
return await tool.handler(params);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
output: '',
|
|
175
|
+
error: `Execution error: ${err.message}`
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
toOpenAITools() {
|
|
180
|
+
return Array.from(this.tools.values()).map(tool => {
|
|
181
|
+
const properties = {};
|
|
182
|
+
const required = [];
|
|
183
|
+
for (const param of tool.definition.parameters) {
|
|
184
|
+
properties[param.name] = {
|
|
185
|
+
type: param.type,
|
|
186
|
+
description: param.description,
|
|
187
|
+
};
|
|
188
|
+
if (param.enum)
|
|
189
|
+
properties[param.name].enum = param.enum;
|
|
190
|
+
if (param.items)
|
|
191
|
+
properties[param.name].items = param.items;
|
|
192
|
+
if (param.required) {
|
|
193
|
+
required.push(param.name);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
type: 'function',
|
|
198
|
+
function: {
|
|
199
|
+
name: tool.definition.name,
|
|
200
|
+
description: tool.definition.description,
|
|
201
|
+
parameters: {
|
|
202
|
+
type: 'object',
|
|
203
|
+
properties,
|
|
204
|
+
required
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
static createDefault() {
|
|
211
|
+
const registry = new ToolRegistry();
|
|
212
|
+
const allTools = [
|
|
213
|
+
...createFilesystemTools(),
|
|
214
|
+
...createSearchTools(),
|
|
215
|
+
...createShellTools(),
|
|
216
|
+
...createGitTools(),
|
|
217
|
+
...createProjectTools(),
|
|
218
|
+
...createDiagnosticTools(),
|
|
219
|
+
...createWebSearchTools(),
|
|
220
|
+
...createMarkdownTools(),
|
|
221
|
+
...createSymbolAndImpactTools(),
|
|
222
|
+
...createCheckpointTools(),
|
|
223
|
+
...mcpRegistry.toOverarchTools(),
|
|
224
|
+
];
|
|
225
|
+
for (const tool of allTools) {
|
|
226
|
+
registry.register(tool);
|
|
227
|
+
}
|
|
228
|
+
return registry;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { PermissionTier } from '../config/types.js';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
function simpleGlobMatch(pattern, text) {
|
|
8
|
+
if (!pattern)
|
|
9
|
+
return true;
|
|
10
|
+
const regexPattern = pattern
|
|
11
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
12
|
+
.replace(/\*/g, '.*')
|
|
13
|
+
.replace(/\?/g, '.');
|
|
14
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
15
|
+
return regex.test(text);
|
|
16
|
+
}
|
|
17
|
+
export function createSearchTools() {
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
definition: {
|
|
21
|
+
name: 'grepSearch',
|
|
22
|
+
description: 'Search for text in files',
|
|
23
|
+
tier: PermissionTier.ReadOnly,
|
|
24
|
+
parameters: [
|
|
25
|
+
{ name: 'pattern', type: 'string', description: 'Text to search for', required: true },
|
|
26
|
+
{ name: 'path', type: 'string', description: 'Path to search in', required: false },
|
|
27
|
+
{ name: 'include', type: 'string', description: 'Glob pattern to include', required: false },
|
|
28
|
+
{ name: 'caseSensitive', type: 'boolean', description: 'Case sensitive search', required: false }
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
handler: async (params) => {
|
|
32
|
+
const targetPath = params.path ? String(params.path) : '.';
|
|
33
|
+
const pattern = String(params.pattern);
|
|
34
|
+
const isWindows = process.platform === 'win32';
|
|
35
|
+
try {
|
|
36
|
+
let output = '';
|
|
37
|
+
if (isWindows) {
|
|
38
|
+
const args = ['/s', '/n'];
|
|
39
|
+
if (!params.caseSensitive)
|
|
40
|
+
args.push('/i');
|
|
41
|
+
args.push(pattern);
|
|
42
|
+
const searchPath = path.join(targetPath, params.include ? String(params.include) : '*.*');
|
|
43
|
+
args.push(searchPath);
|
|
44
|
+
try {
|
|
45
|
+
const { stdout } = await execFileAsync('findstr', args);
|
|
46
|
+
output = stdout;
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
if (e.stdout)
|
|
50
|
+
output = e.stdout;
|
|
51
|
+
else
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const args = ['-rn'];
|
|
57
|
+
if (!params.caseSensitive)
|
|
58
|
+
args.push('-i');
|
|
59
|
+
if (params.include) {
|
|
60
|
+
args.push('--include');
|
|
61
|
+
args.push(String(params.include));
|
|
62
|
+
}
|
|
63
|
+
args.push(pattern);
|
|
64
|
+
args.push(targetPath);
|
|
65
|
+
try {
|
|
66
|
+
const { stdout } = await execFileAsync('grep', args);
|
|
67
|
+
output = stdout;
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
if (e.stdout)
|
|
71
|
+
output = e.stdout;
|
|
72
|
+
else
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const lines = output.split('\n').filter(Boolean).slice(0, 50);
|
|
77
|
+
return { success: true, output: lines.join('\n') || 'No matches found.' };
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
return { success: false, output: '', error: e.message };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
definition: {
|
|
86
|
+
name: 'findFiles',
|
|
87
|
+
description: 'Find files by pattern',
|
|
88
|
+
tier: PermissionTier.ReadOnly,
|
|
89
|
+
parameters: [
|
|
90
|
+
{ name: 'pattern', type: 'string', description: 'Glob pattern', required: true },
|
|
91
|
+
{ name: 'path', type: 'string', description: 'Search path', required: false },
|
|
92
|
+
{ name: 'type', type: 'string', description: 'file, directory, or any', required: false, enum: ['file', 'directory', 'any'] }
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
handler: async (params) => {
|
|
96
|
+
const root = params.path ? String(params.path) : '.';
|
|
97
|
+
const pattern = String(params.pattern);
|
|
98
|
+
const type = params.type || 'any';
|
|
99
|
+
const results = [];
|
|
100
|
+
const ignoreDirs = ['node_modules', '.git', 'dist', 'build'];
|
|
101
|
+
let count = 0;
|
|
102
|
+
async function walk(currentPath) {
|
|
103
|
+
if (count >= 50)
|
|
104
|
+
return;
|
|
105
|
+
try {
|
|
106
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (count >= 50)
|
|
109
|
+
break;
|
|
110
|
+
if (entry.isDirectory() && ignoreDirs.includes(entry.name))
|
|
111
|
+
continue;
|
|
112
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
113
|
+
let matchType = false;
|
|
114
|
+
if (type === 'any')
|
|
115
|
+
matchType = true;
|
|
116
|
+
else if (type === 'file' && entry.isFile())
|
|
117
|
+
matchType = true;
|
|
118
|
+
else if (type === 'directory' && entry.isDirectory())
|
|
119
|
+
matchType = true;
|
|
120
|
+
if (matchType && simpleGlobMatch(pattern, entry.name)) {
|
|
121
|
+
results.push(fullPath);
|
|
122
|
+
count++;
|
|
123
|
+
}
|
|
124
|
+
if (entry.isDirectory()) {
|
|
125
|
+
await walk(fullPath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (e) { }
|
|
130
|
+
}
|
|
131
|
+
await walk(root);
|
|
132
|
+
return { success: true, output: results.join('\n') || 'No files found.' };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
}
|