@c4ccz/zero 1.0.0 → 1.0.1

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 (47) hide show
  1. package/package.json +10 -7
  2. package/packages/core/src/agent/config-loader.ts +174 -0
  3. package/packages/core/src/agent/engine.ts +266 -0
  4. package/packages/core/src/agent/registry-tools.ts +58 -0
  5. package/packages/core/src/agent/registry.ts +213 -0
  6. package/packages/core/src/commands/index.ts +116 -0
  7. package/packages/core/src/config/index.ts +139 -0
  8. package/packages/core/src/confirmation/message-bus.ts +206 -0
  9. package/packages/core/src/diff/index.ts +232 -0
  10. package/packages/core/src/diff-detect/index.ts +207 -0
  11. package/packages/core/src/edit-coder/index.ts +194 -0
  12. package/packages/core/src/events/index.ts +151 -0
  13. package/packages/core/src/file-tracker/index.ts +183 -0
  14. package/packages/core/src/git/index.ts +305 -0
  15. package/packages/core/src/history/index.ts +236 -0
  16. package/packages/core/src/image/index.ts +131 -0
  17. package/packages/core/src/index.ts +131 -0
  18. package/packages/core/src/lsp/index.ts +251 -0
  19. package/packages/core/src/mcp/index.ts +186 -0
  20. package/packages/core/src/memory/index.ts +141 -0
  21. package/packages/core/src/memory/prompts.ts +52 -0
  22. package/packages/core/src/orchestrator/protocol.ts +140 -0
  23. package/packages/core/src/orchestrator/server.ts +319 -0
  24. package/packages/core/src/output/index.ts +164 -0
  25. package/packages/core/src/permissions/index.ts +126 -0
  26. package/packages/core/src/prompts/engine.ts +188 -0
  27. package/packages/core/src/providers/extended.ts +8 -0
  28. package/packages/core/src/providers/registry.ts +687 -0
  29. package/packages/core/src/routing/model-router.ts +160 -0
  30. package/packages/core/src/server/index.ts +232 -0
  31. package/packages/core/src/session/index.ts +174 -0
  32. package/packages/core/src/session/service.ts +322 -0
  33. package/packages/core/src/skills/index.ts +205 -0
  34. package/packages/core/src/streaming/index.ts +166 -0
  35. package/packages/core/src/sub-agent/index.ts +179 -0
  36. package/packages/core/src/tools/builtin.ts +568 -0
  37. package/packages/core/src/tools/linter.ts +26 -0
  38. package/packages/core/src/tools/repo-map.ts +11 -0
  39. package/packages/core/src/types.ts +356 -0
  40. package/packages/sdk/src/index.ts +247 -0
  41. package/packages/tui/src/index.ts +141 -0
  42. package/bun.lock +0 -35
  43. package/bunfig.toml +0 -6
  44. package/packages/cli/package.json +0 -23
  45. package/packages/core/package.json +0 -41
  46. package/packages/sdk/package.json +0 -16
  47. package/packages/tui/package.json +0 -19
@@ -0,0 +1,232 @@
1
+ /**
2
+ * ZERO Diff System
3
+ * Smart file editing with diff generation and application
4
+ *
5
+ * Sources:
6
+ * - aider: search/replace diff editing, unified diff
7
+ * - cline: block-based editing
8
+ * - kilocode: precise file modification
9
+ * - crush: diff detection and application
10
+ */
11
+
12
+ export interface DiffBlock {
13
+ type: "context" | "add" | "remove";
14
+ lineNumber: number;
15
+ content: string;
16
+ }
17
+
18
+ export interface FileDiff {
19
+ filePath: string;
20
+ oldContent: string;
21
+ newContent: string;
22
+ blocks: DiffBlock[];
23
+ stats: { added: number; removed: number; unchanged: number };
24
+ }
25
+
26
+ /**
27
+ * Generate a unified diff between two strings
28
+ */
29
+ export function generateDiff(oldContent: string, newContent: string, filePath = "file"): FileDiff {
30
+ const oldLines = oldContent.split("\n");
31
+ const newLines = newContent.split("\n");
32
+ const blocks: DiffBlock[] = [];
33
+
34
+ let added = 0;
35
+ let removed = 0;
36
+ let unchanged = 0;
37
+
38
+ // Simple LCS-based diff
39
+ const lcs = computeLCS(oldLines, newLines);
40
+ let i = 0;
41
+ let j = 0;
42
+ let k = 0;
43
+
44
+ while (i < oldLines.length || j < newLines.length) {
45
+ if (k < lcs.length && i < oldLines.length && oldLines[i] === lcs[k] && j < newLines.length && newLines[j] === lcs[k]) {
46
+ blocks.push({ type: "context", lineNumber: i + 1, content: oldLines[i] });
47
+ unchanged++;
48
+ i++;
49
+ j++;
50
+ k++;
51
+ } else if (j < newLines.length && (k >= lcs.length || newLines[j] !== lcs[k])) {
52
+ blocks.push({ type: "add", lineNumber: j + 1, content: newLines[j] });
53
+ added++;
54
+ j++;
55
+ } else if (i < oldLines.length && (k >= lcs.length || oldLines[i] !== lcs[k])) {
56
+ blocks.push({ type: "remove", lineNumber: i + 1, content: oldLines[i] });
57
+ removed++;
58
+ i++;
59
+ }
60
+ }
61
+
62
+ return {
63
+ filePath,
64
+ oldContent,
65
+ newContent,
66
+ blocks,
67
+ stats: { added, removed, unchanged },
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Format diff as unified diff string
73
+ */
74
+ export function formatUnifiedDiff(diff: FileDiff): string {
75
+ const lines: string[] = [];
76
+ lines.push(`--- a/${diff.filePath}`);
77
+ lines.push(`+++ b/${diff.filePath}`);
78
+ lines.push(`@@ -1,${diff.oldContent.split("\n").length} +1,${diff.newContent.split("\n").length} @@`);
79
+
80
+ for (const block of diff.blocks) {
81
+ switch (block.type) {
82
+ case "context":
83
+ lines.push(` ${block.content}`);
84
+ break;
85
+ case "add":
86
+ lines.push(`+${block.content}`);
87
+ break;
88
+ case "remove":
89
+ lines.push(`-${block.content}`);
90
+ break;
91
+ }
92
+ }
93
+
94
+ return lines.join("\n");
95
+ }
96
+
97
+ /**
98
+ * Apply a search/replace edit (aider-style)
99
+ */
100
+ export function applySearchReplace(
101
+ content: string,
102
+ search: string,
103
+ replace: string,
104
+ ): { success: boolean; result: string; error?: string } {
105
+ const count = content.split(search).length - 1;
106
+
107
+ if (count === 0) {
108
+ return { success: false, result: content, error: "Search text not found in file" };
109
+ }
110
+
111
+ if (count > 1) {
112
+ return {
113
+ success: false,
114
+ result: content,
115
+ error: `Search text found ${count} times. Provide more context for unique match.`,
116
+ };
117
+ }
118
+
119
+ return {
120
+ success: true,
121
+ result: content.replace(search, replace),
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Apply multiple search/replace blocks (aider-style multi-edit)
127
+ */
128
+ export function applyMultiEdit(
129
+ content: string,
130
+ edits: Array<{ search: string; replace: string }>,
131
+ ): { success: boolean; result: string; errors: string[] } {
132
+ let result = content;
133
+ const errors: string[] = [];
134
+
135
+ for (let i = 0; i < edits.length; i++) {
136
+ const { search, replace } = edits[i];
137
+ const applied = applySearchReplace(result, search, replace);
138
+
139
+ if (!applied.success) {
140
+ errors.push(`Edit ${i + 1}: ${applied.error}`);
141
+ } else {
142
+ result = applied.result;
143
+ }
144
+ }
145
+
146
+ return { success: errors.length === 0, result, errors };
147
+ }
148
+
149
+ /**
150
+ * Smart merge: apply changes while preserving untouched sections
151
+ */
152
+ export function smartMerge(
153
+ original: string,
154
+ modified: string,
155
+ base: string,
156
+ ): { success: boolean; result: string; conflicts: string[] } {
157
+ // Simple three-way merge
158
+ const origLines = original.split("\n");
159
+ const modLines = modified.split("\n");
160
+ const baseLines = base.split("\n");
161
+
162
+ const result: string[] = [];
163
+ const conflicts: string[] = [];
164
+
165
+ const maxLen = Math.max(origLines.length, modLines.length, baseLines.length);
166
+
167
+ for (let i = 0; i < maxLen; i++) {
168
+ const origLine = origLines[i] ?? "";
169
+ const modLine = modLines[i] ?? "";
170
+ const baseLine = baseLines[i] ?? "";
171
+
172
+ if (modLine === baseLine) {
173
+ // No change from base -> use original
174
+ result.push(origLine);
175
+ } else if (origLine === baseLine) {
176
+ // Change from base in modified -> use modified
177
+ result.push(modLine);
178
+ } else if (origLine === modLine) {
179
+ // Same change in both -> use either
180
+ result.push(origLine);
181
+ } else {
182
+ // Conflict
183
+ conflicts.push(`Line ${i + 1}: original="${origLine}" vs modified="${modLine}"`);
184
+ result.push(`<<<<<<< ORIGINAL`);
185
+ result.push(origLine);
186
+ result.push(`=======`);
187
+ result.push(modLine);
188
+ result.push(`>>>>>>> MODIFIED`);
189
+ }
190
+ }
191
+
192
+ return {
193
+ success: conflicts.length === 0,
194
+ result: result.join("\n"),
195
+ conflicts,
196
+ };
197
+ }
198
+
199
+ // Helper: Longest Common Subsequence
200
+ function computeLCS(a: string[], b: string[]): string[] {
201
+ const m = a.length;
202
+ const n = b.length;
203
+ const dp: number[][] = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
204
+
205
+ for (let i = 1; i <= m; i++) {
206
+ for (let j = 1; j <= n; j++) {
207
+ if (a[i - 1] === b[j - 1]) {
208
+ dp[i][j] = dp[i - 1][j - 1] + 1;
209
+ } else {
210
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
211
+ }
212
+ }
213
+ }
214
+
215
+ // Backtrack to find LCS
216
+ const lcs: string[] = [];
217
+ let i = m;
218
+ let j = n;
219
+ while (i > 0 && j > 0) {
220
+ if (a[i - 1] === b[j - 1]) {
221
+ lcs.unshift(a[i - 1]);
222
+ i--;
223
+ j--;
224
+ } else if (dp[i - 1][j] > dp[i][j - 1]) {
225
+ i--;
226
+ } else {
227
+ j--;
228
+ }
229
+ }
230
+
231
+ return lcs;
232
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * ZERO Diff Detect Service
3
+ * Adapted from: crush (Charm) - MIT
4
+ *
5
+ * Detects diffs between file versions and provides
6
+ * intelligent change analysis.
7
+ */
8
+
9
+ import * as fs from "node:fs/promises";
10
+
11
+ export interface DiffStats {
12
+ additions: number;
13
+ deletions: number;
14
+ modifications: number;
15
+ totalChanges: number;
16
+ }
17
+
18
+ export interface DiffHunk {
19
+ oldStart: number;
20
+ oldLines: number;
21
+ newStart: number;
22
+ newLines: number;
23
+ lines: DiffLine[];
24
+ }
25
+
26
+ export interface DiffLine {
27
+ type: "context" | "add" | "remove";
28
+ content: string;
29
+ oldLineNumber?: number;
30
+ newLineNumber?: number;
31
+ }
32
+
33
+ export interface FileDiff {
34
+ filePath: string;
35
+ stats: DiffStats;
36
+ hunks: DiffHunk[];
37
+ }
38
+
39
+ /**
40
+ * Detect diff between two strings
41
+ */
42
+ export function detectDiff(before: string, after: string, filePath = "file"): FileDiff {
43
+ const beforeLines = before.split("\n");
44
+ const afterLines = after.split("\n");
45
+
46
+ const hunks: DiffHunk[] = [];
47
+ let additions = 0;
48
+ let deletions = 0;
49
+ let modifications = 0;
50
+
51
+ // Simple line-by-line diff using LCS
52
+ const lcs = computeLCS(beforeLines, afterLines);
53
+
54
+ let i = 0;
55
+ let j = 0;
56
+ let k = 0;
57
+ const lines: DiffLine[] = [];
58
+
59
+ while (i < beforeLines.length || j < afterLines.length) {
60
+ if (k < lcs.length && i < beforeLines.length && beforeLines[i] === lcs[k] && j < afterLines.length && afterLines[j] === lcs[k]) {
61
+ lines.push({
62
+ type: "context",
63
+ content: beforeLines[i],
64
+ oldLineNumber: i + 1,
65
+ newLineNumber: j + 1,
66
+ });
67
+ i++;
68
+ j++;
69
+ k++;
70
+ } else if (j < afterLines.length && (k >= lcs.length || afterLines[j] !== lcs[k])) {
71
+ lines.push({
72
+ type: "add",
73
+ content: afterLines[j],
74
+ newLineNumber: j + 1,
75
+ });
76
+ additions++;
77
+ j++;
78
+ } else if (i < beforeLines.length && (k >= lcs.length || beforeLines[i] !== lcs[k])) {
79
+ lines.push({
80
+ type: "remove",
81
+ content: beforeLines[i],
82
+ oldLineNumber: i + 1,
83
+ });
84
+ deletions++;
85
+ i++;
86
+ }
87
+ }
88
+
89
+ // Group lines into hunks
90
+ if (lines.length > 0) {
91
+ hunks.push({
92
+ oldStart: 1,
93
+ oldLines: beforeLines.length,
94
+ newStart: 1,
95
+ newLines: afterLines.length,
96
+ lines,
97
+ });
98
+ }
99
+
100
+ // Detect modifications (paired add/remove)
101
+ const minAddDel = Math.min(additions, deletions);
102
+ modifications = minAddDel;
103
+ additions -= minAddDel;
104
+ deletions -= minAddDel;
105
+
106
+ return {
107
+ filePath,
108
+ stats: {
109
+ additions,
110
+ deletions,
111
+ modifications,
112
+ totalChanges: additions + deletions + modifications,
113
+ },
114
+ hunks,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Detect diff between two files
120
+ */
121
+ export async function detectFileDiff(beforePath: string, afterPath: string): Promise<FileDiff> {
122
+ const [before, after] = await Promise.all([
123
+ fs.readFile(beforePath, "utf-8").catch(() => ""),
124
+ fs.readFile(afterPath, "utf-8").catch(() => ""),
125
+ ]);
126
+
127
+ return detectDiff(before, after, afterPath);
128
+ }
129
+
130
+ /**
131
+ * Format diff as unified diff string
132
+ */
133
+ export function formatDiff(diff: FileDiff): string {
134
+ const lines: string[] = [];
135
+ lines.push(`--- a/${diff.filePath}`);
136
+ lines.push(`+++ b/${diff.filePath}`);
137
+
138
+ for (const hunk of diff.hunks) {
139
+ lines.push(`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`);
140
+
141
+ for (const line of hunk.lines) {
142
+ switch (line.type) {
143
+ case "context":
144
+ lines.push(` ${line.content}`);
145
+ break;
146
+ case "add":
147
+ lines.push(`+${line.content}`);
148
+ break;
149
+ case "remove":
150
+ lines.push(`-${line.content}`);
151
+ break;
152
+ }
153
+ }
154
+ }
155
+
156
+ return lines.join("\n");
157
+ }
158
+
159
+ /**
160
+ * Get summary of changes
161
+ */
162
+ export function summarizeDiff(diff: FileDiff): string {
163
+ const { additions, deletions, modifications } = diff.stats;
164
+ const parts: string[] = [];
165
+
166
+ if (additions > 0) parts.push(`+${additions} added`);
167
+ if (deletions > 0) parts.push(`-${deletions} removed`);
168
+ if (modifications > 0) parts.push(`~${modifications} modified`);
169
+
170
+ return parts.length > 0
171
+ ? `${diff.filePath}: ${parts.join(", ")}`
172
+ : `${diff.filePath}: no changes`;
173
+ }
174
+
175
+ // LCS helper
176
+ function computeLCS(a: string[], b: string[]): string[] {
177
+ const m = a.length;
178
+ const n = b.length;
179
+ const dp: number[][] = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
180
+
181
+ for (let i = 1; i <= m; i++) {
182
+ for (let j = 1; j <= n; j++) {
183
+ if (a[i - 1] === b[j - 1]) {
184
+ dp[i][j] = dp[i - 1][j - 1] + 1;
185
+ } else {
186
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
187
+ }
188
+ }
189
+ }
190
+
191
+ const lcs: string[] = [];
192
+ let i = m;
193
+ let j = n;
194
+ while (i > 0 && j > 0) {
195
+ if (a[i - 1] === b[j - 1]) {
196
+ lcs.unshift(a[i - 1]);
197
+ i--;
198
+ j--;
199
+ } else if (dp[i - 1][j] > dp[i][j - 1]) {
200
+ i--;
201
+ } else {
202
+ j--;
203
+ }
204
+ }
205
+
206
+ return lcs;
207
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * ZERO EditBlock Coder
3
+ * Adapted from: aider (Python) - Apache 2.0
4
+ *
5
+ * Aider-style edit block system that uses SEARCH/REPLACE blocks
6
+ * for precise, multi-location file editing.
7
+ *
8
+ * Format:
9
+ * ```
10
+ * <<<<<<< SEARCH
11
+ * exact text to find
12
+ * =======
13
+ * replacement text
14
+ * >>>>>>> REPLACE
15
+ * ```
16
+ */
17
+
18
+ import * as fs from "node:fs/promises";
19
+ import * as path from "node:path";
20
+ import type { ToolDefinition, ToolResult } from "../types.js";
21
+
22
+ export interface EditBlock {
23
+ search: string;
24
+ replace: string;
25
+ lineNumber?: number;
26
+ }
27
+
28
+ export interface EditBlockResult {
29
+ filePath: string;
30
+ applied: number;
31
+ failed: number;
32
+ errors: string[];
33
+ diff: string;
34
+ }
35
+
36
+ /**
37
+ * Parse edit blocks from LLM output
38
+ */
39
+ export function parseEditBlocks(content: string): EditBlock[] {
40
+ const blocks: EditBlock[] = [];
41
+ const regex = /<<<<<<< SEARCH\n([\s\S]*?)=======\n([\s\S]*?)>>>>>>> REPLACE/g;
42
+
43
+ let match;
44
+ while ((match = regex.exec(content)) !== null) {
45
+ blocks.push({
46
+ search: match[1],
47
+ replace: match[2],
48
+ });
49
+ }
50
+
51
+ return blocks;
52
+ }
53
+
54
+ /**
55
+ * Apply edit blocks to file content
56
+ */
57
+ export function applyEditBlocks(content: string, blocks: EditBlock[]): { result: string; applied: number; errors: string[] } {
58
+ let result = content;
59
+ let applied = 0;
60
+ const errors: string[] = [];
61
+
62
+ for (let i = 0; i < blocks.length; i++) {
63
+ const block = blocks[i];
64
+ const searchStr = block.search;
65
+
66
+ // Find the search text
67
+ const idx = result.indexOf(searchStr);
68
+ if (idx === -1) {
69
+ // Try fuzzy match (trim whitespace differences)
70
+ const trimmedSearch = searchStr.trim();
71
+ const lines = result.split("\n");
72
+ let found = false;
73
+
74
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
75
+ const windowSize = trimmedSearch.split("\n").length;
76
+ const window = lines.slice(lineIdx, lineIdx + windowSize).join("\n").trim();
77
+
78
+ if (window === trimmedSearch) {
79
+ // Found a fuzzy match
80
+ const beforeLines = lines.slice(0, lineIdx).join("\n");
81
+ const afterLines = lines.slice(lineIdx + windowSize).join("\n");
82
+ result = (beforeLines ? beforeLines + "\n" : "") + block.replace.trimEnd() + (afterLines ? "\n" + afterLines : "");
83
+ applied++;
84
+ found = true;
85
+ break;
86
+ }
87
+ }
88
+
89
+ if (!found) {
90
+ errors.push(`Block ${i + 1}: Search text not found`);
91
+ }
92
+ } else {
93
+ // Exact match found
94
+ const count = result.split(searchStr).length - 1;
95
+ if (count > 1) {
96
+ errors.push(`Block ${i + 1}: Search text found ${count} times (need unique match)`);
97
+ } else {
98
+ result = result.replace(searchStr, block.replace);
99
+ applied++;
100
+ }
101
+ }
102
+ }
103
+
104
+ return { result, applied, errors };
105
+ }
106
+
107
+ /**
108
+ * Apply edit blocks to a file
109
+ */
110
+ export async function applyEditBlocksToFile(filePath: string, blocks: EditBlock[], workingDir: string): Promise<EditBlockResult> {
111
+ const fullPath = path.resolve(workingDir, filePath);
112
+
113
+ try {
114
+ const content = await fs.readFile(fullPath, "utf-8");
115
+ const { result, applied, errors } = applyEditBlocks(content, blocks);
116
+
117
+ if (applied > 0) {
118
+ await fs.writeFile(fullPath, result, "utf-8");
119
+ }
120
+
121
+ // Generate simple diff
122
+ const diffLines: string[] = [`--- a/${filePath}`, `+++ b/${filePath}`];
123
+ for (const block of blocks.slice(0, applied)) {
124
+ diffLines.push(`-${block.search.split("\n")[0]}`);
125
+ diffLines.push(`+${block.replace.split("\n")[0]}`);
126
+ }
127
+
128
+ return {
129
+ filePath,
130
+ applied,
131
+ failed: blocks.length - applied,
132
+ errors,
133
+ diff: diffLines.join("\n"),
134
+ };
135
+ } catch (error) {
136
+ return {
137
+ filePath,
138
+ applied: 0,
139
+ failed: blocks.length,
140
+ errors: [`Failed to read file: ${error instanceof Error ? error.message : String(error)}`],
141
+ diff: "",
142
+ };
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Generate edit block format for LLM
148
+ */
149
+ export function formatEditBlock(search: string, replace: string): string {
150
+ return `<<<<<<< SEARCH\n${search}=======\n${replace}>>>>>>> REPLACE`;
151
+ }
152
+
153
+ /**
154
+ * EditBlock tool definition
155
+ */
156
+ export const editBlockTool: ToolDefinition = {
157
+ name: "edit_block",
158
+ description: `Apply SEARCH/REPLACE edit blocks to a file. This is the most precise way to edit files.
159
+
160
+ Format your edits as:
161
+ <<<<<<< SEARCH
162
+ exact text to find
163
+ =======
164
+ replacement text
165
+ >>>>>>> REPLACE
166
+
167
+ Multiple edit blocks can be applied in a single call. Each SEARCH block must match exactly once in the file.`,
168
+ parameters: {
169
+ path: { type: "string", description: "Path to the file to edit", required: true },
170
+ edits: { type: "string", description: "Edit blocks in SEARCH/REPLACE format", required: true },
171
+ },
172
+ category: "file",
173
+ requiresApproval: true,
174
+ handler: async (args, context): Promise<ToolResult> => {
175
+ const blocks = parseEditBlocks(args.edits as string);
176
+
177
+ if (blocks.length === 0) {
178
+ return {
179
+ success: false,
180
+ output: "",
181
+ error: "No valid edit blocks found. Use <<<<<<< SEARCH / ======= / >>>>>>> REPLACE format.",
182
+ };
183
+ }
184
+
185
+ const result = await applyEditBlocksToFile(args.path as string, blocks, context.workingDirectory);
186
+
187
+ return {
188
+ success: result.applied > 0,
189
+ output: `Applied ${result.applied}/${blocks.length} edit blocks to ${args.path}${result.errors.length > 0 ? "\nErrors:\n" + result.errors.join("\n") : ""}`,
190
+ data: result,
191
+ error: result.applied === 0 ? result.errors.join("; ") : undefined,
192
+ };
193
+ },
194
+ };