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,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight, zero-dependency line-by-line unified diff generator.
|
|
3
|
+
* Follows the Ponytail principle: efficient, standard library only, edge-case correct.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Computes line-by-line diff between two text strings using standard LCS algorithm.
|
|
7
|
+
*/
|
|
8
|
+
export function computeLineDiff(oldText, newText, contextLines = 3) {
|
|
9
|
+
// Normalize line endings
|
|
10
|
+
const oldLines = oldText === '' ? [] : oldText.replace(/\r\n/g, '\n').split('\n');
|
|
11
|
+
const newLines = newText === '' ? [] : newText.replace(/\r\n/g, '\n').split('\n');
|
|
12
|
+
if (oldText === newText) {
|
|
13
|
+
return { additions: 0, deletions: 0, unifiedDiff: '', hasChanges: false };
|
|
14
|
+
}
|
|
15
|
+
// LCS matrix for line changes (bounded to prevent huge memory usage)
|
|
16
|
+
const maxLines = 1500;
|
|
17
|
+
if (oldLines.length > maxLines || newLines.length > maxLines) {
|
|
18
|
+
// Fast path for very large files: line count comparison
|
|
19
|
+
const additions = Math.max(0, newLines.length - oldLines.length);
|
|
20
|
+
const deletions = Math.max(0, oldLines.length - newLines.length);
|
|
21
|
+
return {
|
|
22
|
+
additions,
|
|
23
|
+
deletions,
|
|
24
|
+
unifiedDiff: `@@ Large file (${oldLines.length} -> ${newLines.length} lines) @@`,
|
|
25
|
+
hasChanges: true
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const m = oldLines.length;
|
|
29
|
+
const n = newLines.length;
|
|
30
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
31
|
+
for (let i = 0; i < m; i++) {
|
|
32
|
+
for (let j = 0; j < n; j++) {
|
|
33
|
+
if (oldLines[i] === newLines[j]) {
|
|
34
|
+
dp[i + 1][j + 1] = dp[i][j] + 1;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const edits = [];
|
|
42
|
+
let i = m;
|
|
43
|
+
let j = n;
|
|
44
|
+
while (i > 0 || j > 0) {
|
|
45
|
+
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
46
|
+
edits.unshift({ type: 'same', line: oldLines[i - 1], oldNum: i, newNum: j });
|
|
47
|
+
i--;
|
|
48
|
+
j--;
|
|
49
|
+
}
|
|
50
|
+
else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
51
|
+
edits.unshift({ type: 'add', line: newLines[j - 1], newNum: j });
|
|
52
|
+
j--;
|
|
53
|
+
}
|
|
54
|
+
else if (i > 0) {
|
|
55
|
+
edits.unshift({ type: 'del', line: oldLines[i - 1], oldNum: i });
|
|
56
|
+
i--;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
let additions = 0;
|
|
60
|
+
let deletions = 0;
|
|
61
|
+
const diffLines = [];
|
|
62
|
+
// Group into hunks with context
|
|
63
|
+
let inHunk = false;
|
|
64
|
+
let hunkBuffer = [];
|
|
65
|
+
for (let k = 0; k < edits.length; k++) {
|
|
66
|
+
const edit = edits[k];
|
|
67
|
+
if (edit.type === 'add')
|
|
68
|
+
additions++;
|
|
69
|
+
if (edit.type === 'del')
|
|
70
|
+
deletions++;
|
|
71
|
+
if (edit.type !== 'same') {
|
|
72
|
+
if (!inHunk) {
|
|
73
|
+
// Collect preceding context
|
|
74
|
+
inHunk = true;
|
|
75
|
+
const start = Math.max(0, k - contextLines);
|
|
76
|
+
hunkBuffer = edits.slice(start, k);
|
|
77
|
+
}
|
|
78
|
+
hunkBuffer.push(edit);
|
|
79
|
+
}
|
|
80
|
+
else if (inHunk) {
|
|
81
|
+
hunkBuffer.push(edit);
|
|
82
|
+
// Check if subsequent lines have changes within context window
|
|
83
|
+
const hasUpcomingChange = edits.slice(k + 1, k + 1 + contextLines * 2).some(e => e.type !== 'same');
|
|
84
|
+
if (!hasUpcomingChange) {
|
|
85
|
+
// Flush hunk
|
|
86
|
+
const trimmedHunk = hunkBuffer.slice(0, hunkBuffer.length);
|
|
87
|
+
const firstEdit = trimmedHunk[0];
|
|
88
|
+
const oldStart = firstEdit.oldNum || 1;
|
|
89
|
+
const newStart = firstEdit.newNum || 1;
|
|
90
|
+
diffLines.push(`@@ -${oldStart} +${newStart} @@`);
|
|
91
|
+
for (const h of trimmedHunk) {
|
|
92
|
+
if (h.type === 'same')
|
|
93
|
+
diffLines.push(` ${h.line}`);
|
|
94
|
+
else if (h.type === 'add')
|
|
95
|
+
diffLines.push(`+ ${h.line}`);
|
|
96
|
+
else if (h.type === 'del')
|
|
97
|
+
diffLines.push(`- ${h.line}`);
|
|
98
|
+
}
|
|
99
|
+
hunkBuffer = [];
|
|
100
|
+
inHunk = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (inHunk && hunkBuffer.length > 0) {
|
|
105
|
+
const firstEdit = hunkBuffer[0];
|
|
106
|
+
const oldStart = firstEdit.oldNum || 1;
|
|
107
|
+
const newStart = firstEdit.newNum || 1;
|
|
108
|
+
diffLines.push(`@@ -${oldStart} +${newStart} @@`);
|
|
109
|
+
for (const h of hunkBuffer) {
|
|
110
|
+
if (h.type === 'same')
|
|
111
|
+
diffLines.push(` ${h.line}`);
|
|
112
|
+
else if (h.type === 'add')
|
|
113
|
+
diffLines.push(`+ ${h.line}`);
|
|
114
|
+
else if (h.type === 'del')
|
|
115
|
+
diffLines.push(`- ${h.line}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
additions,
|
|
120
|
+
deletions,
|
|
121
|
+
unifiedDiff: diffLines.join('\n'),
|
|
122
|
+
hasChanges: additions > 0 || deletions > 0
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { PermissionTier } from '../config/types.js';
|
|
4
|
+
import { computeLineDiff } from './diff.js';
|
|
5
|
+
export function createFilesystemTools() {
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
definition: {
|
|
9
|
+
name: 'readFile',
|
|
10
|
+
description: 'Read contents of a file, optionally with line range',
|
|
11
|
+
tier: PermissionTier.ReadOnly,
|
|
12
|
+
parameters: [
|
|
13
|
+
{ name: 'path', type: 'string', description: 'File path', required: true },
|
|
14
|
+
{ name: 'startLine', type: 'number', description: '1-indexed starting line (optional)', required: false },
|
|
15
|
+
{ name: 'endLine', type: 'number', description: '1-indexed ending line (optional)', required: false }
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
handler: async (params) => {
|
|
19
|
+
const filePath = path.resolve(process.cwd(), String(params.path));
|
|
20
|
+
try {
|
|
21
|
+
const stats = await fs.stat(filePath);
|
|
22
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
23
|
+
const startLine = params.startLine ? Math.max(1, Number(params.startLine)) : undefined;
|
|
24
|
+
const endLine = params.endLine ? Math.max(1, Number(params.endLine)) : undefined;
|
|
25
|
+
if (startLine !== undefined || endLine !== undefined) {
|
|
26
|
+
const lines = content.replace(/\r\n/g, '\n').split('\n');
|
|
27
|
+
const start = (startLine ? startLine - 1 : 0);
|
|
28
|
+
const end = (endLine ? Math.min(lines.length, endLine) : lines.length);
|
|
29
|
+
const sliced = lines.slice(start, end);
|
|
30
|
+
const numbered = sliced.map((line, idx) => `${start + idx + 1} | ${line}`).join('\n');
|
|
31
|
+
return {
|
|
32
|
+
success: true,
|
|
33
|
+
output: numbered,
|
|
34
|
+
metadata: { totalLines: lines.length, startLine: start + 1, endLine: end }
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (stats.size > 100 * 1024) {
|
|
38
|
+
const handle = await fs.open(filePath, 'r');
|
|
39
|
+
const buffer = Buffer.alloc(100 * 1024);
|
|
40
|
+
await handle.read(buffer, 0, 100 * 1024, 0);
|
|
41
|
+
await handle.close();
|
|
42
|
+
return {
|
|
43
|
+
success: true,
|
|
44
|
+
output: buffer.toString('utf8') + '\n... [TRUNCATED - use startLine/endLine for specific sections]',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return { success: true, output: content };
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
return { success: false, output: '', error: e.message };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
definition: {
|
|
56
|
+
name: 'writeFile',
|
|
57
|
+
description: 'Write content to a file. Generates diff if overwriting existing file.',
|
|
58
|
+
tier: PermissionTier.StateMutating,
|
|
59
|
+
parameters: [
|
|
60
|
+
{ name: 'path', type: 'string', description: 'File path', required: true },
|
|
61
|
+
{ name: 'content', type: 'string', description: 'File content', required: true }
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
handler: async (params) => {
|
|
65
|
+
const filePath = path.resolve(process.cwd(), String(params.path));
|
|
66
|
+
const newContent = String(params.content);
|
|
67
|
+
// Checkpoint before mutating file
|
|
68
|
+
const checkpointMgr = new (await import('../tools/checkpoint.js')).CheckpointManager();
|
|
69
|
+
await checkpointMgr.create(`writeFile:${params.path}`, [filePath]);
|
|
70
|
+
try {
|
|
71
|
+
let existingContent = '';
|
|
72
|
+
let fileExisted = false;
|
|
73
|
+
try {
|
|
74
|
+
existingContent = await fs.readFile(filePath, 'utf8');
|
|
75
|
+
fileExisted = true;
|
|
76
|
+
}
|
|
77
|
+
catch { }
|
|
78
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
79
|
+
await fs.writeFile(filePath, newContent, 'utf8');
|
|
80
|
+
const diffResult = computeLineDiff(existingContent, newContent);
|
|
81
|
+
const relPath = path.relative(process.cwd(), filePath);
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
output: fileExisted ? `Updated ${relPath}` : `Created ${relPath}`,
|
|
85
|
+
metadata: {
|
|
86
|
+
path: relPath,
|
|
87
|
+
existed: fileExisted,
|
|
88
|
+
additions: diffResult.additions,
|
|
89
|
+
deletions: diffResult.deletions,
|
|
90
|
+
diff: diffResult.unifiedDiff
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
return { success: false, output: '', error: e.message };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
definition: {
|
|
101
|
+
name: 'editFile',
|
|
102
|
+
description: 'Replace search text with replace text in a file with diff tracking',
|
|
103
|
+
tier: PermissionTier.StateMutating,
|
|
104
|
+
parameters: [
|
|
105
|
+
{ name: 'path', type: 'string', description: 'File path', required: true },
|
|
106
|
+
{ name: 'search', type: 'string', description: 'Exact string to search for', required: true },
|
|
107
|
+
{ name: 'replace', type: 'string', description: 'Exact string to replace with', required: true }
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
handler: async (params) => {
|
|
111
|
+
const filePath = path.resolve(process.cwd(), String(params.path));
|
|
112
|
+
try {
|
|
113
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
114
|
+
const searchStr = String(params.search);
|
|
115
|
+
const replaceStr = String(params.replace);
|
|
116
|
+
let targetFound = content.includes(searchStr);
|
|
117
|
+
let actualSearch = searchStr;
|
|
118
|
+
// If exact match not found, check with normalized line endings
|
|
119
|
+
if (!targetFound) {
|
|
120
|
+
const normalizedContent = content.replace(/\r\n/g, '\n');
|
|
121
|
+
const normalizedSearch = searchStr.replace(/\r\n/g, '\n');
|
|
122
|
+
if (normalizedContent.includes(normalizedSearch)) {
|
|
123
|
+
actualSearch = normalizedSearch;
|
|
124
|
+
targetFound = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!targetFound) {
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
output: '',
|
|
131
|
+
error: `Search string not found in ${params.path}. Use readFile to verify the exact content.`
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const newContent = content.replace(actualSearch, replaceStr);
|
|
135
|
+
await fs.writeFile(filePath, newContent, 'utf8');
|
|
136
|
+
const diffResult = computeLineDiff(content, newContent);
|
|
137
|
+
const relPath = path.relative(process.cwd(), filePath);
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
output: `Replaced in ${relPath}`,
|
|
141
|
+
metadata: {
|
|
142
|
+
path: relPath,
|
|
143
|
+
additions: diffResult.additions,
|
|
144
|
+
deletions: diffResult.deletions,
|
|
145
|
+
diff: diffResult.unifiedDiff
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
return { success: false, output: '', error: e.message };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
definition: {
|
|
156
|
+
name: 'listDirectory',
|
|
157
|
+
description: 'List contents of a directory with file sizes and types',
|
|
158
|
+
tier: PermissionTier.ReadOnly,
|
|
159
|
+
parameters: [
|
|
160
|
+
{ name: 'path', type: 'string', description: 'Directory path', required: true },
|
|
161
|
+
{ name: 'recursive', type: 'boolean', description: 'Recursive listing', required: false }
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
handler: async (params) => {
|
|
165
|
+
const dirPath = path.resolve(process.cwd(), String(params.path));
|
|
166
|
+
const recursive = !!params.recursive;
|
|
167
|
+
const results = [];
|
|
168
|
+
let count = 0;
|
|
169
|
+
const ignore = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', '.next']);
|
|
170
|
+
async function walk(currentPath, depth) {
|
|
171
|
+
if (count >= 200 || depth > 3)
|
|
172
|
+
return;
|
|
173
|
+
try {
|
|
174
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
if (count >= 200)
|
|
177
|
+
break;
|
|
178
|
+
if (ignore.has(entry.name))
|
|
179
|
+
continue;
|
|
180
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
181
|
+
const relPath = path.relative(dirPath, fullPath);
|
|
182
|
+
if (entry.isDirectory()) {
|
|
183
|
+
results.push(`DIR ${relPath}`);
|
|
184
|
+
count++;
|
|
185
|
+
if (recursive)
|
|
186
|
+
await walk(fullPath, depth + 1);
|
|
187
|
+
}
|
|
188
|
+
else if (entry.isFile()) {
|
|
189
|
+
const stats = await fs.stat(fullPath);
|
|
190
|
+
results.push(`FILE ${relPath} (${stats.size} bytes)`);
|
|
191
|
+
count++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
// ignore access errors
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
await walk(dirPath, 1);
|
|
200
|
+
return { success: true, output: results.join('\n') || 'Empty directory' };
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
definition: {
|
|
205
|
+
name: 'createDirectory',
|
|
206
|
+
description: 'Create directory recursively',
|
|
207
|
+
tier: PermissionTier.SafeMutation,
|
|
208
|
+
parameters: [
|
|
209
|
+
{ name: 'path', type: 'string', description: 'Directory path', required: true }
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
handler: async (params) => {
|
|
213
|
+
const dirPath = path.resolve(process.cwd(), String(params.path));
|
|
214
|
+
try {
|
|
215
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
216
|
+
return { success: true, output: `Directory created: ${params.path}` };
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
return { success: false, output: '', error: e.message };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
];
|
|
224
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { PermissionTier } from '../config/types.js';
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export function createGitTools() {
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
definition: {
|
|
9
|
+
name: 'gitStatus',
|
|
10
|
+
description: 'Get git branch, modified files, and staging status',
|
|
11
|
+
tier: PermissionTier.ReadOnly,
|
|
12
|
+
parameters: []
|
|
13
|
+
},
|
|
14
|
+
handler: async () => {
|
|
15
|
+
try {
|
|
16
|
+
const { stdout: branch } = await execFileAsync('git', ['branch', '--show-current']);
|
|
17
|
+
const { stdout: status } = await execFileAsync('git', ['status', '--porcelain']);
|
|
18
|
+
return {
|
|
19
|
+
success: true,
|
|
20
|
+
output: `Branch: ${branch.trim()}\n\nStatus:\n${status || 'Clean working tree'}`
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
return { success: false, output: '', error: e.message };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
definition: {
|
|
30
|
+
name: 'gitDiff',
|
|
31
|
+
description: 'Get git diff for working tree or staged changes',
|
|
32
|
+
tier: PermissionTier.ReadOnly,
|
|
33
|
+
parameters: [
|
|
34
|
+
{ name: 'staged', type: 'boolean', description: 'Diff staged files', required: false },
|
|
35
|
+
{ name: 'path', type: 'string', description: 'Specific path to diff', required: false }
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
handler: async (params) => {
|
|
39
|
+
try {
|
|
40
|
+
const args = ['diff'];
|
|
41
|
+
if (params.staged)
|
|
42
|
+
args.push('--staged');
|
|
43
|
+
if (params.path)
|
|
44
|
+
args.push('--', String(params.path));
|
|
45
|
+
const { stdout } = await execFileAsync('git', args);
|
|
46
|
+
return { success: true, output: stdout.slice(0, 20480) || 'No diff' };
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return { success: false, output: '', error: e.message };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
definition: {
|
|
55
|
+
name: 'gitLog',
|
|
56
|
+
description: 'Get git commit log history',
|
|
57
|
+
tier: PermissionTier.ReadOnly,
|
|
58
|
+
parameters: [
|
|
59
|
+
{ name: 'count', type: 'number', description: 'Number of commits to show', required: false }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
handler: async (params) => {
|
|
63
|
+
try {
|
|
64
|
+
const count = params.count ? Number(params.count) : 10;
|
|
65
|
+
const { stdout } = await execFileAsync('git', ['log', '--oneline', `-n`, String(count)]);
|
|
66
|
+
return { success: true, output: stdout || 'No commits yet.' };
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
return { success: false, output: '', error: e.message };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
definition: {
|
|
75
|
+
name: 'gitBranch',
|
|
76
|
+
description: 'List branches or create a new git branch',
|
|
77
|
+
tier: PermissionTier.SafeMutation,
|
|
78
|
+
parameters: [
|
|
79
|
+
{ name: 'create', type: 'string', description: 'New branch name to create and checkout', required: false }
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
handler: async (params) => {
|
|
83
|
+
try {
|
|
84
|
+
if (params.create) {
|
|
85
|
+
const branchName = String(params.create).trim();
|
|
86
|
+
const { stdout } = await execFileAsync('git', ['checkout', '-b', branchName]);
|
|
87
|
+
return { success: true, output: stdout || `Created and switched to branch ${branchName}` };
|
|
88
|
+
}
|
|
89
|
+
const { stdout } = await execFileAsync('git', ['branch', '-a']);
|
|
90
|
+
return { success: true, output: stdout };
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
return { success: false, output: '', error: e.message };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
definition: {
|
|
99
|
+
name: 'gitStage',
|
|
100
|
+
description: 'Stage files for commit (git add)',
|
|
101
|
+
tier: PermissionTier.SafeMutation,
|
|
102
|
+
parameters: [
|
|
103
|
+
{ name: 'path', type: 'string', description: 'File path to stage, or "." for all', required: false }
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
handler: async (params) => {
|
|
107
|
+
try {
|
|
108
|
+
const target = params.path ? String(params.path) : '.';
|
|
109
|
+
await execFileAsync('git', ['add', target]);
|
|
110
|
+
return { success: true, output: `Staged: ${target}` };
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return { success: false, output: '', error: e.message };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
definition: {
|
|
119
|
+
name: 'gitCommit',
|
|
120
|
+
description: 'Commit staged changes with a descriptive message',
|
|
121
|
+
tier: PermissionTier.StateMutating,
|
|
122
|
+
parameters: [
|
|
123
|
+
{ name: 'message', type: 'string', description: 'Commit message', required: true }
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
handler: async (params) => {
|
|
127
|
+
try {
|
|
128
|
+
const msg = String(params.message);
|
|
129
|
+
const { stdout } = await execFileAsync('git', ['commit', '-m', msg]);
|
|
130
|
+
return { success: true, output: stdout };
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
return { success: false, output: '', error: e.message };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
];
|
|
138
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ToolResult } from './types.js';
|
|
2
|
+
export interface MarkdownHeading {
|
|
3
|
+
level: number;
|
|
4
|
+
text: string;
|
|
5
|
+
line: number;
|
|
6
|
+
}
|
|
7
|
+
export interface MarkdownStructure {
|
|
8
|
+
frontMatter?: Record<string, string>;
|
|
9
|
+
headings: MarkdownHeading[];
|
|
10
|
+
codeBlocksCount: number;
|
|
11
|
+
tablesCount: number;
|
|
12
|
+
rawContent: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parses markdown front matter (YAML-like key: value between --- fences)
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseFrontMatter(content: string): {
|
|
18
|
+
frontMatter?: Record<string, string>;
|
|
19
|
+
body: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Parses markdown structural elements (headings, code blocks, tables)
|
|
23
|
+
*/
|
|
24
|
+
export declare function inspectMarkdown(content: string): MarkdownStructure;
|
|
25
|
+
/**
|
|
26
|
+
* Non-destructive markdown section patcher.
|
|
27
|
+
* Replaces ONLY the target section under a specific heading without corrupting surrounding text.
|
|
28
|
+
*/
|
|
29
|
+
export declare function patchMarkdownSection(filePath: string, targetHeading: string, newSectionContent: string): Promise<ToolResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Terminal syntax renderer for markdown text
|
|
32
|
+
*/
|
|
33
|
+
export declare function renderMarkdown(content: string): string;
|