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,22 @@
1
+ import { Session, SessionMessage, SessionSummary } from './types.js';
2
+ export declare class SessionManager {
3
+ private globalSessionsDir;
4
+ private projectSessionsDir?;
5
+ constructor(sessionsDir?: string, cwd?: string);
6
+ private ensureDir;
7
+ /**
8
+ * Determine primary save location: project-scoped if .overarch exists in project, else global
9
+ */
10
+ private getTargetDir;
11
+ create(cwd: string, model: string): Promise<Session>;
12
+ save(session: Session): Promise<void>;
13
+ load(id: string): Promise<Session | undefined>;
14
+ /**
15
+ * Find a session by exact ID or prefix (e.g. first 4-8 chars)
16
+ */
17
+ findSession(prefixOrId: string): Promise<Session | undefined>;
18
+ list(): Promise<SessionSummary[]>;
19
+ latest(): Promise<Session | undefined>;
20
+ addMessage(session: Session, message: SessionMessage): void;
21
+ delete(id: string): Promise<boolean>;
22
+ }
@@ -0,0 +1,172 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import * as crypto from 'node:crypto';
4
+ import * as os from 'node:os';
5
+ export class SessionManager {
6
+ globalSessionsDir;
7
+ projectSessionsDir;
8
+ constructor(sessionsDir, cwd = process.cwd()) {
9
+ this.globalSessionsDir = sessionsDir || path.join(os.homedir(), '.overarch', 'sessions');
10
+ // If a project has .overarch directory, enable project-scoped sessions
11
+ const localOverarchDir = path.join(cwd, '.overarch');
12
+ this.projectSessionsDir = path.join(localOverarchDir, 'sessions');
13
+ }
14
+ async ensureDir(dir) {
15
+ await fs.mkdir(dir, { recursive: true });
16
+ }
17
+ /**
18
+ * Determine primary save location: project-scoped if .overarch exists in project, else global
19
+ */
20
+ async getTargetDir(cwd) {
21
+ try {
22
+ const localDir = path.join(cwd, '.overarch');
23
+ await fs.access(localDir);
24
+ const projectDir = path.join(localDir, 'sessions');
25
+ await this.ensureDir(projectDir);
26
+ return projectDir;
27
+ }
28
+ catch {
29
+ await this.ensureDir(this.globalSessionsDir);
30
+ return this.globalSessionsDir;
31
+ }
32
+ }
33
+ async create(cwd, model) {
34
+ const id = crypto.randomUUID();
35
+ const now = new Date().toISOString();
36
+ const session = {
37
+ id,
38
+ startedAt: now,
39
+ lastActiveAt: now,
40
+ cwd,
41
+ model,
42
+ messages: [],
43
+ tokenUsage: { prompt: 0, completion: 0, total: 0 }
44
+ };
45
+ await this.save(session);
46
+ return session;
47
+ }
48
+ async save(session) {
49
+ const targetDir = await this.getTargetDir(session.cwd);
50
+ const filePath = path.join(targetDir, `${session.id}.json`);
51
+ await fs.writeFile(filePath, JSON.stringify(session, null, 2), 'utf-8');
52
+ // Also mirror to global directory if stored locally, to ensure global searchability
53
+ if (targetDir !== this.globalSessionsDir) {
54
+ try {
55
+ await this.ensureDir(this.globalSessionsDir);
56
+ const globalPath = path.join(this.globalSessionsDir, `${session.id}.json`);
57
+ await fs.writeFile(globalPath, JSON.stringify(session, null, 2), 'utf-8');
58
+ }
59
+ catch { }
60
+ }
61
+ }
62
+ async load(id) {
63
+ // Search project dir first
64
+ if (this.projectSessionsDir) {
65
+ try {
66
+ const filePath = path.join(this.projectSessionsDir, `${id}.json`);
67
+ const data = await fs.readFile(filePath, 'utf-8');
68
+ return JSON.parse(data);
69
+ }
70
+ catch { }
71
+ }
72
+ // Search global dir
73
+ try {
74
+ const filePath = path.join(this.globalSessionsDir, `${id}.json`);
75
+ const data = await fs.readFile(filePath, 'utf-8');
76
+ return JSON.parse(data);
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ }
82
+ /**
83
+ * Find a session by exact ID or prefix (e.g. first 4-8 chars)
84
+ */
85
+ async findSession(prefixOrId) {
86
+ const clean = prefixOrId.trim().toLowerCase();
87
+ if (!clean)
88
+ return undefined;
89
+ // Try direct exact load
90
+ const exact = await this.load(clean);
91
+ if (exact)
92
+ return exact;
93
+ // Search through all sessions by prefix
94
+ const all = await this.list();
95
+ const match = all.find(s => s.id.toLowerCase().startsWith(clean));
96
+ if (match) {
97
+ return this.load(match.id);
98
+ }
99
+ return undefined;
100
+ }
101
+ async list() {
102
+ const dirs = [this.globalSessionsDir];
103
+ if (this.projectSessionsDir) {
104
+ dirs.unshift(this.projectSessionsDir);
105
+ }
106
+ const seenIds = new Set();
107
+ const summaries = [];
108
+ for (const dir of dirs) {
109
+ try {
110
+ await this.ensureDir(dir);
111
+ const files = await fs.readdir(dir);
112
+ for (const file of files) {
113
+ if (file.endsWith('.json')) {
114
+ const id = file.replace('.json', '');
115
+ if (seenIds.has(id))
116
+ continue;
117
+ seenIds.add(id);
118
+ try {
119
+ const filePath = path.join(dir, file);
120
+ const content = await fs.readFile(filePath, 'utf-8');
121
+ const session = JSON.parse(content);
122
+ summaries.push({
123
+ id: session.id,
124
+ startedAt: session.startedAt,
125
+ lastActiveAt: session.lastActiveAt || session.startedAt,
126
+ cwd: session.cwd,
127
+ model: session.model,
128
+ summary: session.summary,
129
+ messageCount: session.messages?.length || 0
130
+ });
131
+ }
132
+ catch { }
133
+ }
134
+ }
135
+ }
136
+ catch { }
137
+ }
138
+ return summaries.sort((a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime());
139
+ }
140
+ async latest() {
141
+ const sessions = await this.list();
142
+ if (sessions.length > 0) {
143
+ return this.load(sessions[0].id);
144
+ }
145
+ return undefined;
146
+ }
147
+ addMessage(session, message) {
148
+ session.messages.push(message);
149
+ session.lastActiveAt = new Date().toISOString();
150
+ // Auto-generate summary from first user message if not already set
151
+ if (!session.summary && message.role === 'user' && message.content.trim()) {
152
+ const clean = message.content.trim().replace(/\s+/g, ' ');
153
+ session.summary = clean.slice(0, 65) + (clean.length > 65 ? '...' : '');
154
+ }
155
+ }
156
+ async delete(id) {
157
+ let deleted = false;
158
+ if (this.projectSessionsDir) {
159
+ try {
160
+ await fs.unlink(path.join(this.projectSessionsDir, `${id}.json`));
161
+ deleted = true;
162
+ }
163
+ catch { }
164
+ }
165
+ try {
166
+ await fs.unlink(path.join(this.globalSessionsDir, `${id}.json`));
167
+ deleted = true;
168
+ }
169
+ catch { }
170
+ return deleted;
171
+ }
172
+ }
@@ -0,0 +1,36 @@
1
+ export interface Session {
2
+ id: string;
3
+ startedAt: string;
4
+ lastActiveAt: string;
5
+ cwd: string;
6
+ model: string;
7
+ summary?: string;
8
+ messages: SessionMessage[];
9
+ tokenUsage: {
10
+ prompt: number;
11
+ completion: number;
12
+ total: number;
13
+ };
14
+ }
15
+ export interface SessionMessage {
16
+ role: 'user' | 'assistant' | 'tool' | 'system';
17
+ content: string;
18
+ timestamp: string;
19
+ toolCallId?: string;
20
+ toolCalls?: Array<{
21
+ id: string;
22
+ name: string;
23
+ arguments?: Record<string, unknown>;
24
+ params?: Record<string, unknown>;
25
+ result?: string;
26
+ }>;
27
+ }
28
+ export interface SessionSummary {
29
+ id: string;
30
+ startedAt: string;
31
+ lastActiveAt: string;
32
+ cwd: string;
33
+ model: string;
34
+ summary?: string;
35
+ messageCount: number;
36
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { ToolResult } from './types.js';
2
+ export interface CheckpointFileRecord {
3
+ path: string;
4
+ existed: boolean;
5
+ shadowPath?: string;
6
+ }
7
+ export interface CheckpointInfo {
8
+ id: string;
9
+ name: string;
10
+ timestamp: string;
11
+ type: 'git-stash' | 'snapshot';
12
+ gitHash?: string;
13
+ files?: CheckpointFileRecord[];
14
+ shadowDir?: string;
15
+ }
16
+ export declare class CheckpointManager {
17
+ private cwd;
18
+ private checkpointsDir;
19
+ private shadowBaseDir;
20
+ constructor(cwd?: string);
21
+ private ensureDir;
22
+ getShadowDir(): string;
23
+ create(name?: string, files?: string[]): Promise<CheckpointInfo>;
24
+ list(): Promise<CheckpointInfo[]>;
25
+ rollback(targetId?: string): Promise<ToolResult>;
26
+ }
@@ -0,0 +1,160 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { exec } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ const execAsync = promisify(exec);
6
+ export class CheckpointManager {
7
+ cwd;
8
+ checkpointsDir;
9
+ shadowBaseDir;
10
+ constructor(cwd = process.cwd()) {
11
+ this.cwd = cwd;
12
+ this.checkpointsDir = path.join(cwd, '.overarch', 'checkpoints');
13
+ this.shadowBaseDir = path.join(this.checkpointsDir, 'shadow');
14
+ }
15
+ async ensureDir() {
16
+ await fs.mkdir(this.checkpointsDir, { recursive: true });
17
+ await fs.mkdir(this.shadowBaseDir, { recursive: true });
18
+ }
19
+ getShadowDir() {
20
+ return this.shadowBaseDir;
21
+ }
22
+ async create(name = 'checkpoint', files = []) {
23
+ await this.ensureDir();
24
+ const id = `chk_${Date.now()}`;
25
+ const timestamp = new Date().toISOString();
26
+ const targetFiles = [...files];
27
+ if (targetFiles.length === 0 && name.startsWith('writeFile:')) {
28
+ targetFiles.push(path.resolve(this.cwd, name.slice('writeFile:'.length)));
29
+ }
30
+ if (targetFiles.length > 0) {
31
+ const shadowDir = path.join(this.shadowBaseDir, id);
32
+ await fs.mkdir(shadowDir, { recursive: true });
33
+ const recordedFiles = [];
34
+ for (const f of targetFiles) {
35
+ const absPath = path.resolve(this.cwd, f);
36
+ const relPath = path.relative(this.cwd, absPath);
37
+ try {
38
+ const content = await fs.readFile(absPath);
39
+ // Preserve relative subfolder structure in shadow dir
40
+ const safeRelPath = relPath.startsWith('..') ? path.basename(absPath) : relPath;
41
+ const destPath = path.join(shadowDir, safeRelPath);
42
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
43
+ await fs.writeFile(destPath, content);
44
+ recordedFiles.push({ path: relPath, existed: true, shadowPath: destPath });
45
+ }
46
+ catch {
47
+ recordedFiles.push({ path: relPath, existed: false });
48
+ }
49
+ }
50
+ const info = {
51
+ id,
52
+ name,
53
+ timestamp,
54
+ type: 'snapshot',
55
+ files: recordedFiles,
56
+ shadowDir
57
+ };
58
+ await fs.writeFile(path.join(this.checkpointsDir, `${id}.json`), JSON.stringify(info, null, 2), 'utf8');
59
+ return info;
60
+ }
61
+ // Check if git is available
62
+ try {
63
+ const { stdout: isGit } = await execAsync('git rev-parse --is-inside-work-tree', { cwd: this.cwd });
64
+ if (isGit.trim() === 'true') {
65
+ const { stdout: hash } = await execAsync('git rev-parse HEAD', { cwd: this.cwd });
66
+ const stashMsg = `overarch_${id}_${name}`;
67
+ await execAsync(`git stash create "${stashMsg}"`, { cwd: this.cwd });
68
+ const info = {
69
+ id,
70
+ name,
71
+ timestamp,
72
+ type: 'git-stash',
73
+ gitHash: hash.trim()
74
+ };
75
+ await fs.writeFile(path.join(this.checkpointsDir, `${id}.json`), JSON.stringify(info, null, 2), 'utf8');
76
+ return info;
77
+ }
78
+ }
79
+ catch { }
80
+ // Fallback: directory snapshot index of tracked files
81
+ const info = {
82
+ id,
83
+ name,
84
+ timestamp,
85
+ type: 'snapshot'
86
+ };
87
+ await fs.writeFile(path.join(this.checkpointsDir, `${id}.json`), JSON.stringify(info, null, 2), 'utf8');
88
+ return info;
89
+ }
90
+ async list() {
91
+ await this.ensureDir();
92
+ try {
93
+ const files = await fs.readdir(this.checkpointsDir);
94
+ const list = [];
95
+ for (const file of files) {
96
+ if (file.endsWith('.json')) {
97
+ try {
98
+ const data = JSON.parse(await fs.readFile(path.join(this.checkpointsDir, file), 'utf8'));
99
+ list.push(data);
100
+ }
101
+ catch { }
102
+ }
103
+ }
104
+ return list.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
105
+ }
106
+ catch {
107
+ return [];
108
+ }
109
+ }
110
+ async rollback(targetId) {
111
+ const list = await this.list();
112
+ if (list.length === 0) {
113
+ return { success: false, output: '', error: 'No checkpoints found to rollback to.' };
114
+ }
115
+ const target = targetId ? list.find(c => c.id === targetId || c.name === targetId) : list[0];
116
+ if (!target) {
117
+ return { success: false, output: '', error: `Checkpoint '${targetId}' not found.` };
118
+ }
119
+ if (target.files && target.files.length > 0) {
120
+ try {
121
+ for (const file of target.files) {
122
+ const absPath = path.resolve(this.cwd, file.path);
123
+ if (file.existed && file.shadowPath) {
124
+ await fs.mkdir(path.dirname(absPath), { recursive: true });
125
+ await fs.copyFile(file.shadowPath, absPath);
126
+ }
127
+ else if (!file.existed) {
128
+ try {
129
+ await fs.unlink(absPath);
130
+ }
131
+ catch { }
132
+ }
133
+ }
134
+ return {
135
+ success: true,
136
+ output: `Rollback successful: Workspace reset to checkpoint '${target.name}' (${target.id})`
137
+ };
138
+ }
139
+ catch (err) {
140
+ return { success: false, output: '', error: `Shadow rollback failed: ${err.message}` };
141
+ }
142
+ }
143
+ if (target.type === 'git-stash' && target.gitHash) {
144
+ try {
145
+ await execAsync(`git reset --hard ${target.gitHash}`, { cwd: this.cwd });
146
+ return {
147
+ success: true,
148
+ output: `Rollback successful: Workspace reset to checkpoint '${target.name}' (${target.id} at ${target.gitHash.slice(0, 7)})`
149
+ };
150
+ }
151
+ catch (err) {
152
+ return { success: false, output: '', error: `Git rollback failed: ${err.message}` };
153
+ }
154
+ }
155
+ return {
156
+ success: true,
157
+ output: `Restored snapshot checkpoint '${target.name}' (${target.id})`
158
+ };
159
+ }
160
+ }
@@ -0,0 +1,2 @@
1
+ import { Tool } from './types.js';
2
+ export declare function createDiagnosticTools(): Tool[];
@@ -0,0 +1,125 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import * as fs from 'node:fs/promises';
4
+ import { PermissionTier } from '../config/types.js';
5
+ const execFileAsync = promisify(execFile);
6
+ export function createDiagnosticTools() {
7
+ return [
8
+ {
9
+ definition: {
10
+ name: 'runTests',
11
+ description: 'Run project tests',
12
+ tier: PermissionTier.StateMutating,
13
+ parameters: [
14
+ { name: 'command', type: 'string', description: 'Test command to run', required: false }
15
+ ]
16
+ },
17
+ handler: async (params) => {
18
+ let cmd = params.command ? String(params.command) : '';
19
+ const isWindows = process.platform === 'win32';
20
+ const shell = isWindows ? 'powershell.exe' : 'sh';
21
+ const shellArgs = isWindows ? ['-Command'] : ['-c'];
22
+ if (!cmd) {
23
+ try {
24
+ const pkg = JSON.parse(await fs.readFile('package.json', 'utf8'));
25
+ if (pkg.scripts?.test)
26
+ cmd = 'npm test';
27
+ }
28
+ catch (e) { }
29
+ if (!cmd) {
30
+ try {
31
+ await fs.stat('Cargo.toml');
32
+ cmd = 'cargo test';
33
+ }
34
+ catch (e) { }
35
+ }
36
+ }
37
+ if (!cmd) {
38
+ return { success: false, output: '', error: 'Could not detect test command.' };
39
+ }
40
+ try {
41
+ const { stdout, stderr } = await execFileAsync(shell, [...shellArgs, cmd]);
42
+ return { success: true, output: (stdout + '\n' + stderr).slice(0, 20480) };
43
+ }
44
+ catch (e) {
45
+ const output = (e.stdout || '') + '\n' + (e.stderr || '');
46
+ return { success: false, output: output.slice(0, 20480), error: e.message };
47
+ }
48
+ }
49
+ },
50
+ {
51
+ definition: {
52
+ name: 'runTypecheck',
53
+ description: 'Run project typecheck',
54
+ tier: PermissionTier.StateMutating,
55
+ parameters: [
56
+ { name: 'command', type: 'string', description: 'Typecheck command', required: false }
57
+ ]
58
+ },
59
+ handler: async (params) => {
60
+ let cmd = params.command ? String(params.command) : '';
61
+ const isWindows = process.platform === 'win32';
62
+ const shell = isWindows ? 'powershell.exe' : 'sh';
63
+ const shellArgs = isWindows ? ['-Command'] : ['-c'];
64
+ if (!cmd) {
65
+ try {
66
+ const pkg = JSON.parse(await fs.readFile('package.json', 'utf8'));
67
+ if (pkg.scripts?.typecheck)
68
+ cmd = 'npm run typecheck';
69
+ else if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript)
70
+ cmd = 'npx tsc --noEmit';
71
+ }
72
+ catch (e) { }
73
+ }
74
+ if (!cmd) {
75
+ return { success: false, output: '', error: 'Could not detect typecheck command.' };
76
+ }
77
+ try {
78
+ const { stdout, stderr } = await execFileAsync(shell, [...shellArgs, cmd]);
79
+ return { success: true, output: (stdout + '\n' + stderr).slice(0, 20480) };
80
+ }
81
+ catch (e) {
82
+ const output = (e.stdout || '') + '\n' + (e.stderr || '');
83
+ return { success: false, output: output.slice(0, 20480), error: e.message };
84
+ }
85
+ }
86
+ },
87
+ {
88
+ definition: {
89
+ name: 'runLint',
90
+ description: 'Run project linter',
91
+ tier: PermissionTier.StateMutating,
92
+ parameters: [
93
+ { name: 'command', type: 'string', description: 'Lint command', required: false }
94
+ ]
95
+ },
96
+ handler: async (params) => {
97
+ let cmd = params.command ? String(params.command) : '';
98
+ const isWindows = process.platform === 'win32';
99
+ const shell = isWindows ? 'powershell.exe' : 'sh';
100
+ const shellArgs = isWindows ? ['-Command'] : ['-c'];
101
+ if (!cmd) {
102
+ try {
103
+ const pkg = JSON.parse(await fs.readFile('package.json', 'utf8'));
104
+ if (pkg.scripts?.lint)
105
+ cmd = 'npm run lint';
106
+ else if (pkg.devDependencies?.eslint || pkg.dependencies?.eslint)
107
+ cmd = 'npx eslint .';
108
+ }
109
+ catch (e) { }
110
+ }
111
+ if (!cmd) {
112
+ return { success: false, output: '', error: 'Could not detect lint command.' };
113
+ }
114
+ try {
115
+ const { stdout, stderr } = await execFileAsync(shell, [...shellArgs, cmd]);
116
+ return { success: true, output: (stdout + '\n' + stderr).slice(0, 20480) };
117
+ }
118
+ catch (e) {
119
+ const output = (e.stdout || '') + '\n' + (e.stderr || '');
120
+ return { success: false, output: output.slice(0, 20480), error: e.message };
121
+ }
122
+ }
123
+ }
124
+ ];
125
+ }
@@ -0,0 +1,14 @@
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
+ export interface DiffResult {
6
+ additions: number;
7
+ deletions: number;
8
+ unifiedDiff: string;
9
+ hasChanges: boolean;
10
+ }
11
+ /**
12
+ * Computes line-by-line diff between two text strings using standard LCS algorithm.
13
+ */
14
+ export declare function computeLineDiff(oldText: string, newText: string, contextLines?: number): DiffResult;