@oxgeneral/orch 1.0.8 → 1.0.10
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/dist/App-TTMII4MJ.js +22 -0
- package/dist/{chunk-4MMHVHA6.js → chunk-LY3X2UHQ.js} +7 -3
- package/dist/{chunk-SWNSNPBO.js → chunk-OLKQBQ4I.js} +110 -24
- package/dist/chunk-OLKQBQ4I.js.map +1 -0
- package/dist/{chunk-UMZEA3JT.js → chunk-XLBV2PFL.js} +1 -1
- package/dist/{chunk-VG4465AG.js → chunk-XUZZJCKG.js} +6 -2
- package/dist/chunk-XUZZJCKG.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{container-SEIWOLHY.js → container-NS3YS47A.js} +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.js +6 -6
- package/dist/{init-D4356W7G.js → init-6OVZBN6B.js} +1 -1
- package/dist/orchestrator-47LRXS6X.js +6 -0
- package/dist/{orchestrator-G3Y7THMG.js.map → orchestrator-47LRXS6X.js.map} +1 -1
- package/dist/orchestrator-R23BOZDZ.js +17 -0
- package/dist/run-PX7O3ILN.js +3 -0
- package/dist/serve-FTXAEZE4.js +3 -0
- package/dist/{structured-logger-EXMGTUDB.js → structured-logger-RJ6VF35J.js} +2 -2
- package/dist/{task-3R2IX4HM.js → task-HEPJYY7D.js} +1 -1
- package/dist/{template-engine-42PKL5KD.js → template-engine-VLIOJ3WX.js} +1 -1
- package/dist/template-engine-XOH3FZPU.js +3 -0
- package/dist/{template-engine-5ZKVJMYA.js.map → template-engine-XOH3FZPU.js.map} +1 -1
- package/dist/tui-P6ZOPUMZ.js +2 -0
- package/dist/{update-FFKCOV63.js → update-XGJZFV4H.js} +1 -1
- package/dist/{update-check-HGMBDYHL.js → update-check-CZJC7VW6.js} +1 -1
- package/dist/workspace-manager-DG4IFFG3.js +4 -0
- package/dist/{workspace-manager-RH24FSNT.js → workspace-manager-KUU7UMMC.js} +84 -74
- package/dist/workspace-manager-KUU7UMMC.js.map +1 -0
- package/package.json +1 -1
- package/readme.md +2 -2
- package/dist/App-5OVBVRCD.js +0 -22
- package/dist/chunk-SWNSNPBO.js.map +0 -1
- package/dist/chunk-VG4465AG.js.map +0 -1
- package/dist/orchestrator-G3Y7THMG.js +0 -6
- package/dist/orchestrator-GQLNLOXB.js +0 -17
- package/dist/run-NE5E4JPW.js +0 -3
- package/dist/serve-5OAANN6J.js +0 -3
- package/dist/template-engine-5ZKVJMYA.js +0 -3
- package/dist/tui-47O2OCKC.js +0 -2
- package/dist/workspace-manager-RH24FSNT.js.map +0 -1
- package/dist/workspace-manager-VJ4FN5PJ.js +0 -3
|
@@ -91,20 +91,8 @@ var WorkspaceManager = class {
|
|
|
91
91
|
}
|
|
92
92
|
async requireGitRepo(mode) {
|
|
93
93
|
if (!this.gitRepoChecked) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"git",
|
|
97
|
-
["rev-parse", "--is-inside-work-tree"],
|
|
98
|
-
{ cwd: this.projectRoot }
|
|
99
|
-
);
|
|
100
|
-
const code = await new Promise((resolve) => {
|
|
101
|
-
proc.on("close", resolve);
|
|
102
|
-
proc.on("error", () => resolve(1));
|
|
103
|
-
});
|
|
104
|
-
this.isGitRepo = code === 0;
|
|
105
|
-
} catch {
|
|
106
|
-
this.isGitRepo = false;
|
|
107
|
-
}
|
|
94
|
+
const code = await this.spawnAndWait("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
95
|
+
this.isGitRepo = code === 0;
|
|
108
96
|
if (this.isGitRepo) this.gitRepoChecked = true;
|
|
109
97
|
}
|
|
110
98
|
if (!this.isGitRepo) {
|
|
@@ -119,32 +107,9 @@ var WorkspaceManager = class {
|
|
|
119
107
|
}
|
|
120
108
|
async cleanup(taskId, branch) {
|
|
121
109
|
const workspacePath = path.join(this.orchestryDir, "workspaces", sanitizeId(taskId));
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
["worktree", "remove", "--force", workspacePath],
|
|
126
|
-
{ cwd: this.projectRoot }
|
|
127
|
-
);
|
|
128
|
-
await new Promise((resolve) => {
|
|
129
|
-
proc.on("close", () => resolve());
|
|
130
|
-
proc.on("error", () => resolve());
|
|
131
|
-
});
|
|
132
|
-
} catch {
|
|
133
|
-
}
|
|
134
|
-
const branchDeletion = branch ? (async () => {
|
|
135
|
-
try {
|
|
136
|
-
const { process: proc } = this.processManager.spawn(
|
|
137
|
-
"git",
|
|
138
|
-
["branch", "-D", branch],
|
|
139
|
-
{ cwd: this.projectRoot }
|
|
140
|
-
);
|
|
141
|
-
await new Promise((resolve) => {
|
|
142
|
-
proc.on("close", () => resolve());
|
|
143
|
-
proc.on("error", () => resolve());
|
|
144
|
-
});
|
|
145
|
-
} catch {
|
|
146
|
-
}
|
|
147
|
-
})() : Promise.resolve();
|
|
110
|
+
await this.spawnAndWait("git", ["worktree", "remove", "--force", workspacePath]);
|
|
111
|
+
const branchDeletion = branch ? this.spawnAndWait("git", ["branch", "-D", branch]).then(() => {
|
|
112
|
+
}) : Promise.resolve();
|
|
148
113
|
const dirRemoval = fs.rm(workspacePath, { recursive: true, force: true }).catch(() => {
|
|
149
114
|
});
|
|
150
115
|
await Promise.all([branchDeletion, dirRemoval]);
|
|
@@ -152,6 +117,28 @@ var WorkspaceManager = class {
|
|
|
152
117
|
validate(workspacePath, projectRoot) {
|
|
153
118
|
validateWorkspacePath(workspacePath, projectRoot);
|
|
154
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Get files changed on a worktree branch relative to its merge-base.
|
|
122
|
+
* Uses `git merge-base` to find the fork point dynamically (no hardcoded branch name).
|
|
123
|
+
*/
|
|
124
|
+
async getChangedFiles(branch) {
|
|
125
|
+
try {
|
|
126
|
+
const { stdout: baseStdout } = await this.spawnAndCapture(
|
|
127
|
+
"git",
|
|
128
|
+
["merge-base", "HEAD", branch]
|
|
129
|
+
);
|
|
130
|
+
const mergeBase = baseStdout.trim();
|
|
131
|
+
if (!mergeBase) return [];
|
|
132
|
+
const { stdout: diffStdout, code } = await this.spawnAndCapture(
|
|
133
|
+
"git",
|
|
134
|
+
["diff", "--name-only", `${mergeBase}...${branch}`]
|
|
135
|
+
);
|
|
136
|
+
if (code !== 0 || !diffStdout.trim()) return [];
|
|
137
|
+
return diffStdout.trim().split("\n").filter(Boolean);
|
|
138
|
+
} catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
155
142
|
resolveMode(task, agent, config) {
|
|
156
143
|
return task.workspace_mode ?? agent.config.workspace_mode ?? config.defaults.agent.workspace_mode ?? "worktree";
|
|
157
144
|
}
|
|
@@ -164,26 +151,62 @@ var WorkspaceManager = class {
|
|
|
164
151
|
await ensureDir(path.dirname(workspacePath));
|
|
165
152
|
const titleSlug = sanitizeTitle(task.title) || sanitizeId(task.id);
|
|
166
153
|
const branchName = `orchestry/${sanitizeId(task.id)}/${titleSlug}`;
|
|
167
|
-
|
|
154
|
+
try {
|
|
155
|
+
await fs.access(workspacePath);
|
|
156
|
+
return { path: workspacePath, branch: branchName };
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
const createResult = await this.spawnAndWait(
|
|
168
160
|
"git",
|
|
169
|
-
["worktree", "add", workspacePath, "-b", branchName]
|
|
170
|
-
{ cwd: this.projectRoot }
|
|
161
|
+
["worktree", "add", workspacePath, "-b", branchName]
|
|
171
162
|
);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
163
|
+
if (createResult !== 0) {
|
|
164
|
+
await this.spawnAndWait("git", ["worktree", "prune"]);
|
|
165
|
+
const reuseResult = await this.spawnAndWait(
|
|
166
|
+
"git",
|
|
167
|
+
["worktree", "add", workspacePath, branchName]
|
|
168
|
+
);
|
|
169
|
+
if (reuseResult !== 0) {
|
|
170
|
+
throw new WorkspaceError(
|
|
171
|
+
`git worktree add failed with code ${reuseResult}`,
|
|
177
172
|
"Run: git worktree prune && git branch | grep orchestry | xargs -r git branch -D"
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
});
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
182
176
|
const worktreeOrchestry = path.join(workspacePath, ".orchestry");
|
|
183
177
|
await fs.rm(worktreeOrchestry, { recursive: true, force: true }).catch(() => {
|
|
184
178
|
});
|
|
185
179
|
return { path: workspacePath, branch: branchName };
|
|
186
180
|
}
|
|
181
|
+
/** Spawn a command and return exit code (non-throwing). */
|
|
182
|
+
async spawnAndWait(cmd, args) {
|
|
183
|
+
try {
|
|
184
|
+
const { process: proc } = this.processManager.spawn(cmd, args, { cwd: this.projectRoot });
|
|
185
|
+
return new Promise((resolve) => {
|
|
186
|
+
proc.on("close", (code) => resolve(code ?? 1));
|
|
187
|
+
proc.on("error", () => resolve(1));
|
|
188
|
+
});
|
|
189
|
+
} catch {
|
|
190
|
+
return 1;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Spawn a command and capture stdout + exit code. */
|
|
194
|
+
async spawnAndCapture(cmd, args) {
|
|
195
|
+
try {
|
|
196
|
+
const { process: proc } = this.processManager.spawn(cmd, args, { cwd: this.projectRoot });
|
|
197
|
+
let stdout = "";
|
|
198
|
+
proc.stdout?.on("data", (chunk) => {
|
|
199
|
+
stdout += chunk.toString();
|
|
200
|
+
});
|
|
201
|
+
const code = await new Promise((resolve) => {
|
|
202
|
+
proc.on("close", (c) => resolve(c ?? 1));
|
|
203
|
+
proc.on("error", () => resolve(1));
|
|
204
|
+
});
|
|
205
|
+
return { stdout, code };
|
|
206
|
+
} catch {
|
|
207
|
+
return { stdout: "", code: 1 };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
187
210
|
async prepareIsolated(task) {
|
|
188
211
|
const workspacePath = path.join(
|
|
189
212
|
this.orchestryDir,
|
|
@@ -192,31 +215,18 @@ var WorkspaceManager = class {
|
|
|
192
215
|
);
|
|
193
216
|
await ensureDir(path.dirname(workspacePath));
|
|
194
217
|
try {
|
|
195
|
-
const
|
|
218
|
+
const cloneResult = await this.spawnAndWait(
|
|
196
219
|
"git",
|
|
197
|
-
["clone", "--local", "--no-hardlinks",
|
|
198
|
-
{ cwd: this.projectRoot }
|
|
220
|
+
["clone", "--local", "--no-hardlinks", this.projectRoot, workspacePath]
|
|
199
221
|
);
|
|
200
|
-
|
|
201
|
-
proc.on("close", (code) => {
|
|
202
|
-
if (code === 0) resolve();
|
|
203
|
-
else reject(new Error("git clone failed"));
|
|
204
|
-
});
|
|
205
|
-
proc.on("error", reject);
|
|
206
|
-
});
|
|
222
|
+
if (cloneResult !== 0) throw new Error("git clone failed");
|
|
207
223
|
} catch {
|
|
208
224
|
const excludeFile = path.join(this.orchestryDir, "workspace-exclude");
|
|
209
225
|
const args = ["-a", `--exclude-from=${excludeFile}`, "./", `${workspacePath}/`];
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
proc.on("close", (code) => {
|
|
215
|
-
if (code === 0) resolve();
|
|
216
|
-
else reject(new Error(`rsync failed with code ${code}`));
|
|
217
|
-
});
|
|
218
|
-
proc.on("error", reject);
|
|
219
|
-
});
|
|
226
|
+
const rsyncResult = await this.spawnAndWait("rsync", args);
|
|
227
|
+
if (rsyncResult !== 0) {
|
|
228
|
+
throw new Error(`rsync failed with code ${rsyncResult}`);
|
|
229
|
+
}
|
|
220
230
|
}
|
|
221
231
|
const clonedOrchestry = path.join(workspacePath, ".orchestry");
|
|
222
232
|
await fs.rm(clonedOrchestry, { recursive: true, force: true }).catch(() => {
|
|
@@ -229,5 +239,5 @@ function sanitizeTitle(title) {
|
|
|
229
239
|
}
|
|
230
240
|
|
|
231
241
|
export { WorkspaceManager };
|
|
232
|
-
//# sourceMappingURL=workspace-manager-
|
|
233
|
-
//# sourceMappingURL=workspace-manager-
|
|
242
|
+
//# sourceMappingURL=workspace-manager-KUU7UMMC.js.map
|
|
243
|
+
//# sourceMappingURL=workspace-manager-KUU7UMMC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/infrastructure/workspace/merge-strategy.ts","../src/infrastructure/workspace/workspace-manager.ts"],"names":[],"mappings":"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","file":"workspace-manager-KUU7UMMC.js","sourcesContent":["/**\n * Git merge strategy for worktree branches.\n *\n * Encapsulates `git merge --no-ff` execution and conflict handling.\n */\n\nimport type { IProcessManager } from '../process/process-manager.js';\n\nexport type MergeResult =\n | { success: true }\n | { success: false; conflictInfo: string };\n\nexport class MergeStrategy {\n constructor(\n private readonly projectRoot: string,\n private readonly processManager: IProcessManager,\n ) {}\n\n /**\n * Merge a branch into the current branch with --no-ff.\n * On conflict, aborts the merge and returns conflict info.\n */\n async mergeBack(branch: string): Promise<MergeResult> {\n return new Promise((resolve) => {\n const { process: proc } = this.processManager.spawn(\n 'git',\n ['merge', '--no-ff', branch, '-m', `Merge ${branch}`],\n { cwd: this.projectRoot },\n );\n\n let output = '';\n const maxOutputLen = 2000;\n const appendOutput = (chunk: Buffer) => {\n if (output.length < maxOutputLen) output += chunk.toString();\n };\n proc.stdout?.on('data', appendOutput);\n proc.stderr?.on('data', appendOutput);\n\n proc.on('close', (code) => {\n if (code === 0) {\n resolve({ success: true });\n return;\n }\n\n const trimmedOutput = output.slice(0, 1000);\n const isConflict = trimmedOutput.includes('CONFLICT') || trimmedOutput.includes('Merge conflict');\n\n if (!isConflict) {\n // Non-conflict failure (branch not found, hook failure, etc.) — no merge to abort\n resolve({ success: false, conflictInfo: trimmedOutput });\n return;\n }\n\n // Abort the failed merge to restore clean state\n try {\n const { process: abortProc } = this.processManager.spawn(\n 'git',\n ['merge', '--abort'],\n { cwd: this.projectRoot },\n );\n abortProc.on('close', () => {\n resolve({ success: false, conflictInfo: trimmedOutput });\n });\n abortProc.on('error', () => {\n resolve({ success: false, conflictInfo: trimmedOutput });\n });\n } catch {\n resolve({ success: false, conflictInfo: trimmedOutput });\n }\n });\n\n proc.on('error', (err) => {\n resolve({ success: false, conflictInfo: err.message });\n });\n });\n }\n}\n","/**\n * Workspace manager implementation.\n *\n * Resolves workspace path based on mode priority chain:\n * task.workspace_mode → agent.config.workspace_mode → defaults.agent.workspace_mode → 'worktree'\n */\n\nimport path from 'node:path';\nimport fs from 'node:fs/promises';\nimport type { Agent } from '../../domain/agent.js';\nimport type { OrchestratorConfig } from '../../domain/config.js';\nimport type { Task, WorkspaceMode } from '../../domain/task.js';\nimport type { IProcessManager } from '../process/process-manager.js';\nimport { validateWorkspacePath, sanitizeId } from '../storage/paths.js';\nimport { ensureDir } from '../storage/fs-utils.js';\nimport type { IWorkspaceManager, PrepareResult } from './interface.js';\nimport { MergeStrategy, type MergeResult } from './merge-strategy.js';\nimport { WorkspaceError } from '../../domain/errors.js';\n\nexport class WorkspaceManager implements IWorkspaceManager {\n private readonly mergeStrategy: MergeStrategy;\n private gitRepoChecked = false;\n private isGitRepo = false;\n\n constructor(\n private readonly projectRoot: string,\n private readonly orchestryDir: string,\n private readonly processManager: IProcessManager,\n ) {\n this.mergeStrategy = new MergeStrategy(projectRoot, processManager);\n }\n\n async prepare(task: Task, agent: Agent, config: OrchestratorConfig): Promise<PrepareResult> {\n const mode = this.resolveMode(task, agent, config);\n\n if (mode !== 'shared') {\n await this.requireGitRepo(mode);\n }\n\n switch (mode) {\n case 'shared':\n return { path: this.projectRoot };\n\n case 'worktree':\n return this.prepareWorktree(task);\n\n case 'isolated':\n return { path: await this.prepareIsolated(task) };\n\n default:\n return { path: this.projectRoot };\n }\n }\n\n private async requireGitRepo(mode: WorkspaceMode): Promise<void> {\n if (!this.gitRepoChecked) {\n const code = await this.spawnAndWait('git', ['rev-parse', '--is-inside-work-tree']);\n this.isGitRepo = code === 0;\n // Only cache positive result — negative may change if user runs git init\n if (this.isGitRepo) this.gitRepoChecked = true;\n }\n\n if (!this.isGitRepo) {\n throw new WorkspaceError(\n `workspace_mode \"${mode}\" requires a git repository`,\n 'Run: git init && git add -A && git commit -m \"Initial commit\"\\n Or set workspace_mode: shared in .orchestry/config.yml',\n );\n }\n }\n\n async mergeBack(branch: string): Promise<MergeResult> {\n return this.mergeStrategy.mergeBack(branch);\n }\n\n async cleanup(taskId: string, branch?: string): Promise<void> {\n const workspacePath = path.join(this.orchestryDir, 'workspaces', sanitizeId(taskId));\n\n // Try git worktree remove first (cleans up .git/worktrees/ metadata)\n await this.spawnAndWait('git', ['worktree', 'remove', '--force', workspacePath]);\n\n // Delete branch + remove directory concurrently\n const branchDeletion = branch\n ? this.spawnAndWait('git', ['branch', '-D', branch]).then(() => {})\n : Promise.resolve();\n\n const dirRemoval = fs.rm(workspacePath, { recursive: true, force: true }).catch(() => {});\n\n await Promise.all([branchDeletion, dirRemoval]);\n }\n\n validate(workspacePath: string, projectRoot: string): void {\n validateWorkspacePath(workspacePath, projectRoot);\n }\n\n /**\n * Get files changed on a worktree branch relative to its merge-base.\n * Uses `git merge-base` to find the fork point dynamically (no hardcoded branch name).\n */\n async getChangedFiles(branch: string): Promise<string[]> {\n try {\n const { stdout: baseStdout } = await this.spawnAndCapture(\n 'git', ['merge-base', 'HEAD', branch],\n );\n const mergeBase = baseStdout.trim();\n if (!mergeBase) return [];\n\n const { stdout: diffStdout, code } = await this.spawnAndCapture(\n 'git', ['diff', '--name-only', `${mergeBase}...${branch}`],\n );\n if (code !== 0 || !diffStdout.trim()) return [];\n return diffStdout.trim().split('\\n').filter(Boolean);\n } catch {\n return [];\n }\n }\n\n private resolveMode(task: Task, agent: Agent, config: OrchestratorConfig): WorkspaceMode {\n return (\n task.workspace_mode ??\n agent.config.workspace_mode ??\n config.defaults.agent.workspace_mode ??\n 'worktree'\n );\n }\n\n private async prepareWorktree(task: Task): Promise<PrepareResult> {\n const workspacePath = path.join(\n this.orchestryDir,\n 'workspaces',\n sanitizeId(task.id),\n );\n await ensureDir(path.dirname(workspacePath));\n\n const titleSlug = sanitizeTitle(task.title) || sanitizeId(task.id);\n const branchName = `orchestry/${sanitizeId(task.id)}/${titleSlug}`;\n\n // Idempotent: if worktree directory already exists (retry after failure), reuse it\n try {\n await fs.access(workspacePath);\n return { path: workspacePath, branch: branchName };\n } catch {\n // Directory doesn't exist — create fresh\n }\n\n // Try creating worktree: first with new branch (-b), fallback to existing branch\n const createResult = await this.spawnAndWait(\n 'git', ['worktree', 'add', workspacePath, '-b', branchName],\n );\n if (createResult !== 0) {\n // Branch may already exist from a previous failed run — prune stale metadata and retry\n await this.spawnAndWait('git', ['worktree', 'prune']);\n const reuseResult = await this.spawnAndWait(\n 'git', ['worktree', 'add', workspacePath, branchName],\n );\n if (reuseResult !== 0) {\n throw new WorkspaceError(\n `git worktree add failed with code ${reuseResult}`,\n 'Run: git worktree prune && git branch | grep orchestry | xargs -r git branch -D',\n );\n }\n }\n\n // Remove .orchestry/ from worktree to prevent recursive state/workspaces\n const worktreeOrchestry = path.join(workspacePath, '.orchestry');\n await fs.rm(worktreeOrchestry, { recursive: true, force: true }).catch(() => {});\n\n return { path: workspacePath, branch: branchName };\n }\n\n /** Spawn a command and return exit code (non-throwing). */\n private async spawnAndWait(cmd: string, args: string[]): Promise<number> {\n try {\n const { process: proc } = this.processManager.spawn(cmd, args, { cwd: this.projectRoot });\n return new Promise<number>((resolve) => {\n proc.on('close', (code) => resolve(code ?? 1));\n proc.on('error', () => resolve(1));\n });\n } catch {\n return 1;\n }\n }\n\n /** Spawn a command and capture stdout + exit code. */\n private async spawnAndCapture(cmd: string, args: string[]): Promise<{ stdout: string; code: number }> {\n try {\n const { process: proc } = this.processManager.spawn(cmd, args, { cwd: this.projectRoot });\n let stdout = '';\n proc.stdout?.on('data', (chunk: Buffer) => { stdout += chunk.toString(); });\n const code = await new Promise<number>((resolve) => {\n proc.on('close', (c) => resolve(c ?? 1));\n proc.on('error', () => resolve(1));\n });\n return { stdout, code };\n } catch {\n return { stdout: '', code: 1 };\n }\n }\n\n private async prepareIsolated(task: Task): Promise<string> {\n const workspacePath = path.join(\n this.orchestryDir,\n 'workspaces',\n sanitizeId(task.id),\n );\n await ensureDir(path.dirname(workspacePath));\n\n // Try git clone first, fall back to rsync\n try {\n const cloneResult = await this.spawnAndWait(\n 'git', ['clone', '--local', '--no-hardlinks', this.projectRoot, workspacePath],\n );\n if (cloneResult !== 0) throw new Error('git clone failed');\n } catch {\n // Fallback: rsync\n const excludeFile = path.join(this.orchestryDir, 'workspace-exclude');\n const args = ['-a', `--exclude-from=${excludeFile}`, './', `${workspacePath}/`];\n\n const rsyncResult = await this.spawnAndWait('rsync', args);\n if (rsyncResult !== 0) {\n throw new Error(`rsync failed with code ${rsyncResult}`);\n }\n }\n\n // Remove .orchestry/ to prevent recursive workspaces (covers both clone and rsync)\n const clonedOrchestry = path.join(workspacePath, '.orchestry');\n await fs.rm(clonedOrchestry, { recursive: true, force: true }).catch(() => {});\n\n return workspacePath;\n }\n}\n\nfunction sanitizeTitle(title: string): string {\n return title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 40);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxgeneral/orch",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "AI agent runtime — orchestrate Claude, Cursor, Codex & OpenCode as one team. Multi-agent task automation with state machine, auto-retry, inter-agent messaging, goals and teams. Zero-config CLI + programmatic API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
package/readme.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<a href="https://www.orch.one/"><img src="https://img.shields.io/badge/website-orch.one-f59e0b?style=for-the-badge&labelColor=0a0a0a" alt="Website" /></a>
|
|
16
16
|
<a href="https://www.npmjs.com/package/@oxgeneral/orch"><img src="https://img.shields.io/npm/v/@oxgeneral/orch?style=for-the-badge&color=f59e0b&labelColor=0a0a0a" alt="npm" /></a>
|
|
17
17
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-f59e0b?style=for-the-badge&labelColor=0a0a0a" alt="MIT License" /></a>
|
|
18
|
-
<a href="#development"><img src="https://img.shields.io/badge/tests-
|
|
18
|
+
<a href="#development"><img src="https://img.shields.io/badge/tests-1694%20passing-f59e0b?style=for-the-badge&labelColor=0a0a0a" alt="Tests" /></a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
21
|
<br/>
|
|
@@ -621,7 +621,7 @@ src/
|
|
|
621
621
|
```bash
|
|
622
622
|
npm run dev # Run via tsx
|
|
623
623
|
npm run build # Build ESM + DTS
|
|
624
|
-
npm test #
|
|
624
|
+
npm test # 1694 tests via Vitest
|
|
625
625
|
npm run typecheck # Strict TypeScript
|
|
626
626
|
```
|
|
627
627
|
|