orcastrator 0.2.13 → 0.2.15
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/README.md +75 -4
- package/dist/agents/claude/session.js +55 -0
- package/dist/agents/codex/session.js +55 -0
- package/dist/cli/commands/cancel.js +1 -1
- package/dist/cli/commands/run.js +27 -6
- package/dist/cli/commands/setup.js +2 -4
- package/dist/core/config-loader.js +23 -1
- package/dist/core/planner.js +152 -10
- package/dist/core/task-graph-review.js +132 -0
- package/dist/hooks/adapters/stdout.js +2 -2
- package/dist/hooks/dispatcher.js +4 -1
- package/dist/utils/agent-json.js +3 -3
- package/package.json +13 -7
package/README.md
CHANGED
|
@@ -22,7 +22,17 @@ Start with a plain-language goal:
|
|
|
22
22
|
orca "add auth to the app"
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Orca will create a run, plan tasks, execute
|
|
25
|
+
Orca will create a run, plan tasks, run a pre-execution review/improvement pass on the task graph, execute the reviewed graph, and persist run state.
|
|
26
|
+
|
|
27
|
+
### Pre-execution review-improvement stage
|
|
28
|
+
|
|
29
|
+
After planning, Orca runs a structured review pass that can edit the task graph before execution starts. The review output is schema-validated and supports concrete graph operations:
|
|
30
|
+
|
|
31
|
+
- update task fields (`name`, `description`, `acceptance_criteria`)
|
|
32
|
+
- add/remove task
|
|
33
|
+
- add/remove dependency
|
|
34
|
+
|
|
35
|
+
The edited graph is re-validated as a DAG. If review output is invalid, Orca fails with an actionable error by default. You can configure `review.onInvalid: "warn_skip"` to log a warning and continue with the original planner graph.
|
|
26
36
|
|
|
27
37
|
## Spec And Plan Files
|
|
28
38
|
|
|
@@ -92,6 +102,10 @@ export default {
|
|
|
92
102
|
codex: {
|
|
93
103
|
model: "gpt-5.3-codex", // override the codex model
|
|
94
104
|
multiAgent: true, // enable codex multi-agent (see below)
|
|
105
|
+
},
|
|
106
|
+
review: {
|
|
107
|
+
enabled: true, // default true
|
|
108
|
+
onInvalid: "fail" // or "warn_skip"
|
|
95
109
|
}
|
|
96
110
|
};
|
|
97
111
|
```
|
|
@@ -135,6 +149,7 @@ Global:
|
|
|
135
149
|
- `--on-milestone <cmd>`
|
|
136
150
|
- `--on-task-complete <cmd>`
|
|
137
151
|
- `--on-task-fail <cmd>`
|
|
152
|
+
- `--on-invalid-plan <cmd>`
|
|
138
153
|
- `--on-complete <cmd>`
|
|
139
154
|
- `--on-error <cmd>`
|
|
140
155
|
|
|
@@ -188,7 +203,7 @@ Global:
|
|
|
188
203
|
|
|
189
204
|
- `--anthropic-key <key>`
|
|
190
205
|
- `--openai-key <key>`
|
|
191
|
-
- `--check` (API key lookup order: CLI flag → process env → `~/.openclaw/openclaw.json` `env.vars` → `~/.claude/.env` → `~/.config/claude/.env`
|
|
206
|
+
- `--check` (API key lookup order: CLI flag → process env → `~/.openclaw/openclaw.json` `env.vars` → `~/.claude/.env` → `~/.config/claude/.env`)
|
|
192
207
|
- `--global`
|
|
193
208
|
- `--project`
|
|
194
209
|
|
|
@@ -204,6 +219,7 @@ Hook names:
|
|
|
204
219
|
- `onMilestone`
|
|
205
220
|
- `onTaskComplete`
|
|
206
221
|
- `onTaskFail`
|
|
222
|
+
- `onInvalidPlan`
|
|
207
223
|
- `onComplete`
|
|
208
224
|
- `onError`
|
|
209
225
|
|
|
@@ -222,6 +238,15 @@ Run IDs are generated as:
|
|
|
222
238
|
- Project: `./orca.config.js` or `./orca.config.ts`
|
|
223
239
|
- Explicit: `--config <path>`
|
|
224
240
|
|
|
241
|
+
### Project Instruction Files
|
|
242
|
+
|
|
243
|
+
During planning, Orca automatically injects project instruction files when present:
|
|
244
|
+
|
|
245
|
+
1. `AGENTS.md`
|
|
246
|
+
2. `CLAUDE.md`
|
|
247
|
+
|
|
248
|
+
Files are discovered from the project root (nearest `.git` from the spec/task context) and injected in that order.
|
|
249
|
+
|
|
225
250
|
### Run State Locations
|
|
226
251
|
|
|
227
252
|
- Run status: `<runsDir>/<run-id>/status.json`
|
|
@@ -230,8 +255,54 @@ Run IDs are generated as:
|
|
|
230
255
|
|
|
231
256
|
## Development
|
|
232
257
|
|
|
258
|
+
Install dependencies with npm (primary lockfile):
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
npm install
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Run local development and tests with Bun (faster runtime for this project):
|
|
265
|
+
|
|
233
266
|
```bash
|
|
234
|
-
bun install
|
|
235
|
-
bun test
|
|
236
267
|
bun run src/cli/index.ts "your goal here"
|
|
268
|
+
bun test src
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Validation pipeline
|
|
272
|
+
|
|
273
|
+
Use the full validation gate before opening/publishing changes:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
npm run validate
|
|
237
277
|
```
|
|
278
|
+
|
|
279
|
+
This runs, in order:
|
|
280
|
+
|
|
281
|
+
1. `npm run lint` (Oxlint syntax/style/static rules)
|
|
282
|
+
2. `npm run lint:type-aware` (Oxlint + tsgolint alpha type-aware + type-check diagnostics)
|
|
283
|
+
3. `npm run typecheck` (TypeScript Native Preview via `tsgo --noEmit`, with environment fallback to `tsc --noEmit`)
|
|
284
|
+
4. `npm run test`
|
|
285
|
+
5. `npm run build`
|
|
286
|
+
|
|
287
|
+
`npm run build` remains `tsc` because the native preview compiler is used here as a fast typecheck gate; production JS emission stays on stable `typescript` for predictable package output.
|
|
288
|
+
|
|
289
|
+
## Package manager + lockfile policy
|
|
290
|
+
|
|
291
|
+
Orca uses a mixed runtime/tooling model on purpose:
|
|
292
|
+
|
|
293
|
+
- **npm is canonical for dependency resolution, release builds, and deterministic installs**.
|
|
294
|
+
- **Bun is used as a runtime/test runner in local workflows** (`dev`, `start`, `test`).
|
|
295
|
+
|
|
296
|
+
Commit both lockfiles:
|
|
297
|
+
|
|
298
|
+
- `package-lock.json` — canonical dependency graph for npm/CI/publish
|
|
299
|
+
- `bun.lock` — Bun runtime resolution parity for local Bun commands
|
|
300
|
+
|
|
301
|
+
When dependencies change, update both lockfiles in the same PR:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
npm install
|
|
305
|
+
bun install
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
This keeps npm and Bun behavior aligned without forcing a disruptive full migration.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { TaskGraphReviewPayloadSchema } from "../../core/task-graph-review.js";
|
|
3
4
|
import { parseAgentJson } from "../../utils/agent-json.js";
|
|
4
5
|
const PlannedTaskSchema = z.object({
|
|
5
6
|
id: z.string().min(1),
|
|
@@ -101,6 +102,23 @@ const EXECUTION_OUTPUT_FORMAT = {
|
|
|
101
102
|
type: "json_schema",
|
|
102
103
|
schema: EXECUTION_OUTPUT_SCHEMA,
|
|
103
104
|
};
|
|
105
|
+
const REVIEW_OUTPUT_SCHEMA = {
|
|
106
|
+
type: "object",
|
|
107
|
+
additionalProperties: false,
|
|
108
|
+
required: ["changes"],
|
|
109
|
+
properties: {
|
|
110
|
+
changes: {
|
|
111
|
+
type: "array",
|
|
112
|
+
items: {
|
|
113
|
+
type: "object"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const REVIEW_OUTPUT_FORMAT = {
|
|
119
|
+
type: "json_schema",
|
|
120
|
+
schema: REVIEW_OUTPUT_SCHEMA,
|
|
121
|
+
};
|
|
104
122
|
function buildPlanningPrompt(spec, systemContext) {
|
|
105
123
|
return [
|
|
106
124
|
systemContext,
|
|
@@ -127,6 +145,27 @@ function buildTaskExecutionPrompt(task, runId, cwd, systemContext) {
|
|
|
127
145
|
"If you cannot complete the task, set outcome=failed and provide a concise error.",
|
|
128
146
|
].join("\n\n");
|
|
129
147
|
}
|
|
148
|
+
function buildTaskGraphReviewPrompt(tasks, systemContext) {
|
|
149
|
+
return [
|
|
150
|
+
systemContext,
|
|
151
|
+
"You are Orca's pre-execution task-graph reviewer.",
|
|
152
|
+
"Return only structured review operations in the configured schema.",
|
|
153
|
+
"Allowed operations: update_task (name/description/acceptance_criteria), add_task, remove_task, add_dependency, remove_dependency.",
|
|
154
|
+
"Return an empty changes array if no edits are needed.",
|
|
155
|
+
"Current task graph JSON:",
|
|
156
|
+
JSON.stringify(tasks, null, 2)
|
|
157
|
+
].join("\n\n");
|
|
158
|
+
}
|
|
159
|
+
function parseStructuredTaskGraphReviewPayload(payload, rawResponse = "") {
|
|
160
|
+
const result = TaskGraphReviewPayloadSchema.safeParse(payload);
|
|
161
|
+
if (!result.success) {
|
|
162
|
+
throw formatSchemaError("Claude structured review payload failed schema validation", result.error);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
changes: result.data.changes,
|
|
166
|
+
rawResponse
|
|
167
|
+
};
|
|
168
|
+
}
|
|
130
169
|
function extractAssistantText(message) {
|
|
131
170
|
if (!message || typeof message !== "object") {
|
|
132
171
|
return null;
|
|
@@ -277,6 +316,22 @@ export async function planSpec(spec, systemContext, config) {
|
|
|
277
316
|
claudeQuery.close();
|
|
278
317
|
}
|
|
279
318
|
}
|
|
319
|
+
export async function reviewTaskGraph(tasks, systemContext, config) {
|
|
320
|
+
const claudeQuery = query({
|
|
321
|
+
prompt: buildTaskGraphReviewPrompt(tasks, systemContext),
|
|
322
|
+
options: buildClaudeQueryOptions(config, REVIEW_OUTPUT_FORMAT),
|
|
323
|
+
});
|
|
324
|
+
try {
|
|
325
|
+
const { rawResponse, structuredOutput } = await collectSessionResult(claudeQuery);
|
|
326
|
+
if (structuredOutput === undefined) {
|
|
327
|
+
throwMissingStructuredOutput("review");
|
|
328
|
+
}
|
|
329
|
+
return parseStructuredTaskGraphReviewPayload(structuredOutput, rawResponse);
|
|
330
|
+
}
|
|
331
|
+
finally {
|
|
332
|
+
claudeQuery.close();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
280
335
|
export async function executeTask(task, runId, config, systemContext) {
|
|
281
336
|
const claudeQuery = query({
|
|
282
337
|
prompt: buildTaskExecutionPrompt(task, runId, process.cwd(), systemContext),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CodexClient } from "@ratley/codex-client";
|
|
2
|
+
import { TaskGraphReviewPayloadSchema } from "../../core/task-graph-review.js";
|
|
2
3
|
function buildPlanningPrompt(spec, systemContext) {
|
|
3
4
|
return [
|
|
4
5
|
systemContext,
|
|
@@ -33,6 +34,36 @@ function buildTaskExecutionPrompt(task, runId, cwd, systemContext) {
|
|
|
33
34
|
"Do not wrap it in markdown fences. Do not add any text after the JSON line. The JSON line is required.",
|
|
34
35
|
].join("\n\n");
|
|
35
36
|
}
|
|
37
|
+
function buildTaskGraphReviewPrompt(tasks, systemContext) {
|
|
38
|
+
return [
|
|
39
|
+
systemContext,
|
|
40
|
+
"You are Orca's pre-execution task-graph reviewer.",
|
|
41
|
+
"Return JSON matching this shape exactly: {\"changes\":[...operations...]}",
|
|
42
|
+
"Allowed operation shapes:",
|
|
43
|
+
"- {\"op\":\"update_task\",\"taskId\":\"...\",\"fields\":{\"name\"?:string,\"description\"?:string,\"acceptance_criteria\"?:string[]}}",
|
|
44
|
+
"- {\"op\":\"add_task\",\"task\":<full task object>}",
|
|
45
|
+
"- {\"op\":\"remove_task\",\"taskId\":\"...\"}",
|
|
46
|
+
"- {\"op\":\"add_dependency\",\"taskId\":\"...\",\"dependsOn\":\"...\"}",
|
|
47
|
+
"- {\"op\":\"remove_dependency\",\"taskId\":\"...\",\"dependsOn\":\"...\"}",
|
|
48
|
+
"Return ONLY JSON. No markdown.",
|
|
49
|
+
"Current task graph:",
|
|
50
|
+
JSON.stringify(tasks, null, 2),
|
|
51
|
+
].join("\n\n");
|
|
52
|
+
}
|
|
53
|
+
function parseTaskGraphReview(raw) {
|
|
54
|
+
const parsed = JSON.parse(extractJson(raw));
|
|
55
|
+
const result = TaskGraphReviewPayloadSchema.safeParse(parsed);
|
|
56
|
+
if (!result.success) {
|
|
57
|
+
const details = result.error.issues
|
|
58
|
+
.map((issue) => `${issue.path.length > 0 ? issue.path.join(".") : "<root>"}: ${issue.message}`)
|
|
59
|
+
.join("; ");
|
|
60
|
+
throw new Error(`Codex review response failed schema validation. ${details}`);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
changes: result.data.changes,
|
|
64
|
+
rawResponse: raw,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
36
67
|
function extractAgentText(result) {
|
|
37
68
|
if (result.agentMessage.length > 0) {
|
|
38
69
|
return result.agentMessage;
|
|
@@ -192,6 +223,21 @@ export async function createCodexSession(cwd, config) {
|
|
|
192
223
|
rawResponse,
|
|
193
224
|
};
|
|
194
225
|
},
|
|
226
|
+
async reviewTaskGraph(tasks, systemContext) {
|
|
227
|
+
const effort = getEffort(config);
|
|
228
|
+
const result = effort
|
|
229
|
+
? await client.runTurn({
|
|
230
|
+
threadId,
|
|
231
|
+
effort,
|
|
232
|
+
input: [{ type: "text", text: buildTaskGraphReviewPrompt(tasks, systemContext) }],
|
|
233
|
+
})
|
|
234
|
+
: await client.runTurn({
|
|
235
|
+
threadId,
|
|
236
|
+
input: [{ type: "text", text: buildTaskGraphReviewPrompt(tasks, systemContext) }],
|
|
237
|
+
});
|
|
238
|
+
const rawResponse = extractAgentText(result);
|
|
239
|
+
return parseTaskGraphReview(rawResponse);
|
|
240
|
+
},
|
|
195
241
|
async executeTask(task, runId, systemContext) {
|
|
196
242
|
const effort = getEffort(config);
|
|
197
243
|
const result = effort
|
|
@@ -299,6 +345,15 @@ export async function planSpec(spec, systemContext, config) {
|
|
|
299
345
|
await session.disconnect();
|
|
300
346
|
}
|
|
301
347
|
}
|
|
348
|
+
export async function reviewTaskGraph(tasks, systemContext, config) {
|
|
349
|
+
const session = await createCodexSession(process.cwd(), config);
|
|
350
|
+
try {
|
|
351
|
+
return await session.reviewTaskGraph(tasks, systemContext);
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
await session.disconnect();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
302
357
|
export async function executeTask(task, runId, config, systemContext) {
|
|
303
358
|
const session = await createCodexSession(process.cwd(), config);
|
|
304
359
|
try {
|
|
@@ -37,7 +37,7 @@ export async function cancelCommandHandler(options) {
|
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
const cancelledAt = new Date().toISOString();
|
|
40
|
-
let cancelledTaskId
|
|
40
|
+
let cancelledTaskId;
|
|
41
41
|
const tasks = run.tasks.map((task) => {
|
|
42
42
|
if (task.status === "in_progress") {
|
|
43
43
|
cancelledTaskId = task.id;
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -7,7 +7,7 @@ import { InvalidArgumentError } from "commander";
|
|
|
7
7
|
import { createCodexSession } from "../../agents/codex/session.js";
|
|
8
8
|
import { ensureCodexMultiAgent } from "../../core/codex-config.js";
|
|
9
9
|
import { resolveConfig } from "../../core/config-loader.js";
|
|
10
|
-
import { runPlanner } from "../../core/planner.js";
|
|
10
|
+
import { InvalidPlanError, runPlanner } from "../../core/planner.js";
|
|
11
11
|
import { runTaskRunner } from "../../core/task-runner.js";
|
|
12
12
|
import { createOpenclawHookHandler, detectOpenclawAvailability } from "../../hooks/adapters/openclaw.js";
|
|
13
13
|
import { createStdoutHookHandler } from "../../hooks/adapters/stdout.js";
|
|
@@ -19,6 +19,7 @@ const ALL_HOOKS = [
|
|
|
19
19
|
"onMilestone",
|
|
20
20
|
"onTaskComplete",
|
|
21
21
|
"onTaskFail",
|
|
22
|
+
"onInvalidPlan",
|
|
22
23
|
"onComplete",
|
|
23
24
|
"onError"
|
|
24
25
|
];
|
|
@@ -26,6 +27,7 @@ const VALID_HOOK_NAMES = new Set([
|
|
|
26
27
|
"onMilestone",
|
|
27
28
|
"onTaskComplete",
|
|
28
29
|
"onTaskFail",
|
|
30
|
+
"onInvalidPlan",
|
|
29
31
|
"onComplete",
|
|
30
32
|
"onError"
|
|
31
33
|
]);
|
|
@@ -63,6 +65,7 @@ function buildCliCommandHooks(options) {
|
|
|
63
65
|
...(options.onMilestone ? { onMilestone: options.onMilestone } : {}),
|
|
64
66
|
...(options.onTaskComplete ? { onTaskComplete: options.onTaskComplete } : {}),
|
|
65
67
|
...(options.onTaskFail ? { onTaskFail: options.onTaskFail } : {}),
|
|
68
|
+
...(options.onInvalidPlan ? { onInvalidPlan: options.onInvalidPlan } : {}),
|
|
66
69
|
...(options.onComplete ? { onComplete: options.onComplete } : {}),
|
|
67
70
|
...(options.onError ? { onError: options.onError } : {})
|
|
68
71
|
};
|
|
@@ -118,11 +121,6 @@ export async function runCommandHandler(options) {
|
|
|
118
121
|
console.log(`Run ID: ${runId}`);
|
|
119
122
|
const store = createStore();
|
|
120
123
|
await store.createRun(runId, specPath);
|
|
121
|
-
await runPlanner(specPath, store, runId, effectiveConfig);
|
|
122
|
-
await store.updateRun(runId, {
|
|
123
|
-
mode: "run",
|
|
124
|
-
overallStatus: "running"
|
|
125
|
-
});
|
|
126
124
|
const cliCommandHooks = buildCliCommandHooks(options);
|
|
127
125
|
const dispatcher = new HookDispatcher({
|
|
128
126
|
commandHooks: {
|
|
@@ -162,6 +160,28 @@ export async function runCommandHandler(options) {
|
|
|
162
160
|
const emitHook = async (event) => {
|
|
163
161
|
await dispatcher.dispatch(event);
|
|
164
162
|
};
|
|
163
|
+
try {
|
|
164
|
+
await runPlanner(specPath, store, runId, effectiveConfig);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
if (error instanceof InvalidPlanError) {
|
|
168
|
+
await emitHook({
|
|
169
|
+
runId: runId,
|
|
170
|
+
hook: "onInvalidPlan",
|
|
171
|
+
message: `invalid-plan:${error.stage}`,
|
|
172
|
+
timestamp: new Date().toISOString(),
|
|
173
|
+
error: error.message,
|
|
174
|
+
metadata: {
|
|
175
|
+
stage: error.stage
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
await store.updateRun(runId, {
|
|
182
|
+
mode: "run",
|
|
183
|
+
overallStatus: "running"
|
|
184
|
+
});
|
|
165
185
|
const executor = effectiveConfig?.executor ?? "codex";
|
|
166
186
|
if (executor === "codex") {
|
|
167
187
|
const cwd = process.cwd();
|
|
@@ -253,6 +273,7 @@ export function registerRunCommand(program) {
|
|
|
253
273
|
.option("--on-milestone <cmd>", "Shell hook command for onMilestone")
|
|
254
274
|
.option("--on-task-complete <cmd>", "Shell hook command for onTaskComplete")
|
|
255
275
|
.option("--on-task-fail <cmd>", "Shell hook command for onTaskFail")
|
|
276
|
+
.option("--on-invalid-plan <cmd>", "Shell hook command for onInvalidPlan")
|
|
256
277
|
.option("--on-complete <cmd>", "Shell hook command for onComplete")
|
|
257
278
|
.option("--on-error <cmd>", "Shell hook command for onError")
|
|
258
279
|
.action(async (goal, commandOptions) => {
|
|
@@ -57,7 +57,7 @@ export function resolveApiKey(flagValue, envVarName, openclawConfigPathOrOptions
|
|
|
57
57
|
return envValue.trim();
|
|
58
58
|
}
|
|
59
59
|
const options = typeof openclawConfigPathOrOptions === "string"
|
|
60
|
-
? { ...
|
|
60
|
+
? { ...maybeOptions, openclawConfigPath: openclawConfigPathOrOptions }
|
|
61
61
|
: (openclawConfigPathOrOptions ?? {});
|
|
62
62
|
const homedir = options.homedir ?? os.homedir();
|
|
63
63
|
const openclawValue = readOpenclawEnvVar(envVarName, options.openclawConfigPath, homedir);
|
|
@@ -65,7 +65,6 @@ export function resolveApiKey(flagValue, envVarName, openclawConfigPathOrOptions
|
|
|
65
65
|
return openclawValue;
|
|
66
66
|
}
|
|
67
67
|
const dotenvValue = readDotEnvFallback(envVarName, {
|
|
68
|
-
cwd: options.cwd ?? process.cwd(),
|
|
69
68
|
homedir
|
|
70
69
|
});
|
|
71
70
|
if (dotenvValue) {
|
|
@@ -104,8 +103,7 @@ function readOpenclawEnvVar(envVarName, openclawConfigPath, homedir = os.homedir
|
|
|
104
103
|
function readDotEnvFallback(envVarName, options) {
|
|
105
104
|
const candidatePaths = [
|
|
106
105
|
path.join(options.homedir, ".claude", ".env"),
|
|
107
|
-
path.join(options.homedir, ".config", "claude", ".env")
|
|
108
|
-
path.join(options.cwd, ".env")
|
|
106
|
+
path.join(options.homedir, ".config", "claude", ".env")
|
|
109
107
|
];
|
|
110
108
|
for (const candidatePath of candidatePaths) {
|
|
111
109
|
const value = readEnvVarFromDotEnvFile(candidatePath, envVarName);
|
|
@@ -52,7 +52,10 @@ function coerceConfig(candidate) {
|
|
|
52
52
|
}
|
|
53
53
|
if ("executor" in candidate && candidate.executor !== undefined) {
|
|
54
54
|
if (candidate.executor !== "claude" && candidate.executor !== "codex") {
|
|
55
|
-
|
|
55
|
+
const executorDisplay = typeof candidate.executor === "string"
|
|
56
|
+
? candidate.executor
|
|
57
|
+
: (JSON.stringify(candidate.executor) ?? describeType(candidate.executor));
|
|
58
|
+
throw new Error(`Config.executor must be 'claude' or 'codex', got ${executorDisplay}`);
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
if ("claude" in candidate && candidate.claude !== undefined) {
|
|
@@ -77,6 +80,22 @@ function coerceConfig(candidate) {
|
|
|
77
80
|
parseCodexEffort(candidate.codex.effort);
|
|
78
81
|
}
|
|
79
82
|
}
|
|
83
|
+
if ("review" in candidate && candidate.review !== undefined) {
|
|
84
|
+
if (!isObject(candidate.review)) {
|
|
85
|
+
throw new Error(`Config.review must be an object, got ${describeType(candidate.review)}`);
|
|
86
|
+
}
|
|
87
|
+
if ("enabled" in candidate.review && candidate.review.enabled !== undefined && typeof candidate.review.enabled !== "boolean") {
|
|
88
|
+
throw new Error(`Config.review.enabled must be a boolean, got ${describeType(candidate.review.enabled)}`);
|
|
89
|
+
}
|
|
90
|
+
if ("onInvalid" in candidate.review && candidate.review.onInvalid !== undefined) {
|
|
91
|
+
if (candidate.review.onInvalid !== "fail" && candidate.review.onInvalid !== "warn_skip") {
|
|
92
|
+
const onInvalidDisplay = typeof candidate.review.onInvalid === "string"
|
|
93
|
+
? candidate.review.onInvalid
|
|
94
|
+
: (JSON.stringify(candidate.review.onInvalid) ?? describeType(candidate.review.onInvalid));
|
|
95
|
+
throw new Error(`Config.review.onInvalid must be 'fail' or 'warn_skip', got ${onInvalidDisplay}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
80
99
|
return candidate;
|
|
81
100
|
}
|
|
82
101
|
export async function loadConfig(configPath) {
|
|
@@ -112,6 +131,9 @@ export function mergeConfigs(...configs) {
|
|
|
112
131
|
if (merged.pr !== undefined || config.pr !== undefined) {
|
|
113
132
|
merged.pr = { ...merged.pr, ...config.pr };
|
|
114
133
|
}
|
|
134
|
+
if (merged.review !== undefined || config.review !== undefined) {
|
|
135
|
+
merged.review = { ...merged.review, ...config.review };
|
|
136
|
+
}
|
|
115
137
|
if (merged.hooks !== undefined || config.hooks !== undefined) {
|
|
116
138
|
merged.hooks = { ...merged.hooks, ...config.hooks };
|
|
117
139
|
}
|
package/dist/core/planner.js
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
|
-
import
|
|
3
|
-
import { planSpec as
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { planSpec as planSpecWithClaude, reviewTaskGraph as reviewTaskGraphWithClaude } from "../agents/claude/session.js";
|
|
4
|
+
import { planSpec as planSpecWithCodex, reviewTaskGraph as reviewTaskGraphWithCodex } from "../agents/codex/session.js";
|
|
4
5
|
import { logger } from "../utils/logger.js";
|
|
5
6
|
import { loadSkills } from "../utils/skill-loader.js";
|
|
6
7
|
import { validateDAG } from "./dependency-graph.js";
|
|
8
|
+
import { applyTaskGraphReviewChanges, summarizeReviewChanges } from "./task-graph-review.js";
|
|
7
9
|
const DEFAULT_SYSTEM_CONTEXT = "You are Orca planner.";
|
|
10
|
+
const PROJECT_INSTRUCTION_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
11
|
+
const PROJECT_INSTRUCTION_CHAR_CAP = 4_000;
|
|
12
|
+
export class InvalidPlanError extends Error {
|
|
13
|
+
stage;
|
|
14
|
+
constructor(stage, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "InvalidPlanError";
|
|
17
|
+
this.stage = stage;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
8
20
|
let testPlanSpecOverride = null;
|
|
21
|
+
let testReviewTaskGraphOverride = null;
|
|
9
22
|
export function setPlanSpecForTests(fn) {
|
|
10
23
|
testPlanSpecOverride = fn;
|
|
11
24
|
}
|
|
25
|
+
export function setReviewTaskGraphForTests(fn) {
|
|
26
|
+
testReviewTaskGraphOverride = fn;
|
|
27
|
+
}
|
|
12
28
|
function resolvePlanSpecImpl(config) {
|
|
13
29
|
if (testPlanSpecOverride) {
|
|
14
30
|
return testPlanSpecOverride;
|
|
@@ -16,6 +32,13 @@ function resolvePlanSpecImpl(config) {
|
|
|
16
32
|
const executor = config?.executor ?? "codex";
|
|
17
33
|
return executor === "claude" ? planSpecWithClaude : planSpecWithCodex;
|
|
18
34
|
}
|
|
35
|
+
function resolveReviewTaskGraphImpl(config) {
|
|
36
|
+
if (testReviewTaskGraphOverride) {
|
|
37
|
+
return testReviewTaskGraphOverride;
|
|
38
|
+
}
|
|
39
|
+
const executor = config?.executor ?? "codex";
|
|
40
|
+
return executor === "claude" ? reviewTaskGraphWithClaude : reviewTaskGraphWithCodex;
|
|
41
|
+
}
|
|
19
42
|
function formatSkillsSection(skills) {
|
|
20
43
|
const formattedSkills = skills.map((skill) => [
|
|
21
44
|
`### ${skill.name}`,
|
|
@@ -26,20 +49,139 @@ function formatSkillsSection(skills) {
|
|
|
26
49
|
].join("\n"));
|
|
27
50
|
return ["## Available Skills", "", ...formattedSkills].join("\n");
|
|
28
51
|
}
|
|
52
|
+
async function pathExists(targetPath) {
|
|
53
|
+
try {
|
|
54
|
+
await fs.access(targetPath);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function resolveProjectContextDir(specPath) {
|
|
62
|
+
let currentDir = path.dirname(path.resolve(specPath));
|
|
63
|
+
while (true) {
|
|
64
|
+
const gitMarker = path.join(currentDir, ".git");
|
|
65
|
+
if (await pathExists(gitMarker)) {
|
|
66
|
+
return currentDir;
|
|
67
|
+
}
|
|
68
|
+
const parent = path.dirname(currentDir);
|
|
69
|
+
if (parent === currentDir) {
|
|
70
|
+
return path.dirname(path.resolve(specPath));
|
|
71
|
+
}
|
|
72
|
+
currentDir = parent;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function loadProjectInstructions(specPath) {
|
|
76
|
+
const projectDir = await resolveProjectContextDir(specPath);
|
|
77
|
+
const instructions = [];
|
|
78
|
+
for (const fileName of PROJECT_INSTRUCTION_FILES) {
|
|
79
|
+
const filePath = path.join(projectDir, fileName);
|
|
80
|
+
if (!(await pathExists(filePath))) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const rawContent = await fs.readFile(filePath, "utf8");
|
|
84
|
+
const content = rawContent.slice(0, PROJECT_INSTRUCTION_CHAR_CAP);
|
|
85
|
+
instructions.push({
|
|
86
|
+
fileName,
|
|
87
|
+
filePath,
|
|
88
|
+
content,
|
|
89
|
+
truncated: rawContent.length > PROJECT_INSTRUCTION_CHAR_CAP
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return instructions;
|
|
93
|
+
}
|
|
94
|
+
function formatProjectInstructionsSection(instructions) {
|
|
95
|
+
const parts = ["## Project Instructions"];
|
|
96
|
+
for (const instruction of instructions) {
|
|
97
|
+
parts.push("");
|
|
98
|
+
parts.push(`### ${instruction.fileName} (${instruction.filePath})`);
|
|
99
|
+
parts.push("");
|
|
100
|
+
parts.push("```md");
|
|
101
|
+
parts.push(instruction.content);
|
|
102
|
+
parts.push("```");
|
|
103
|
+
if (instruction.truncated) {
|
|
104
|
+
parts.push(`(truncated to ${PROJECT_INSTRUCTION_CHAR_CAP} characters)`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return parts.join("\n");
|
|
108
|
+
}
|
|
109
|
+
function buildSystemContext(skills, instructions) {
|
|
110
|
+
const sections = [DEFAULT_SYSTEM_CONTEXT];
|
|
111
|
+
if (instructions.length > 0) {
|
|
112
|
+
sections.push(formatProjectInstructionsSection(instructions));
|
|
113
|
+
}
|
|
114
|
+
if (skills.length > 0) {
|
|
115
|
+
sections.push(formatSkillsSection(skills));
|
|
116
|
+
}
|
|
117
|
+
return sections.join("\n\n");
|
|
118
|
+
}
|
|
119
|
+
async function runTaskGraphReview(tasks, systemContext, config) {
|
|
120
|
+
if (config?.review?.enabled === false) {
|
|
121
|
+
return { finalTasks: tasks, review: null };
|
|
122
|
+
}
|
|
123
|
+
logger.info("Review started: pre-execution task graph improvement pass");
|
|
124
|
+
const reviewFn = resolveReviewTaskGraphImpl(config);
|
|
125
|
+
let review;
|
|
126
|
+
try {
|
|
127
|
+
review = await reviewFn(tasks, systemContext, config);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (config?.review?.onInvalid === "warn_skip") {
|
|
131
|
+
logger.warn(`Review output invalid; skipping review changes (${error instanceof Error ? error.message : String(error)})`);
|
|
132
|
+
return { finalTasks: tasks, review: null };
|
|
133
|
+
}
|
|
134
|
+
throw new InvalidPlanError("review", `Review output invalid. ${error instanceof Error ? error.message : String(error)}`);
|
|
135
|
+
}
|
|
136
|
+
if (review.changes.length === 0) {
|
|
137
|
+
logger.info("Review made no changes");
|
|
138
|
+
return { finalTasks: tasks, review };
|
|
139
|
+
}
|
|
140
|
+
const updated = applyTaskGraphReviewChanges(tasks, review.changes);
|
|
141
|
+
try {
|
|
142
|
+
validateDAG(updated);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
throw new InvalidPlanError("review", error instanceof Error ? error.message : String(error));
|
|
146
|
+
}
|
|
147
|
+
const summary = summarizeReviewChanges(review.changes).join("; ");
|
|
148
|
+
logger.success(`Review made ${review.changes.length} changes: ${summary}`);
|
|
149
|
+
return { finalTasks: updated, review };
|
|
150
|
+
}
|
|
29
151
|
export async function runPlanner(specPath, store, runId, config) {
|
|
30
152
|
const spec = await fs.readFile(specPath, "utf8");
|
|
31
|
-
const skills = await loadSkills(config);
|
|
32
|
-
const systemContext = skills
|
|
33
|
-
? DEFAULT_SYSTEM_CONTEXT
|
|
34
|
-
: `${DEFAULT_SYSTEM_CONTEXT}\n\n${formatSkillsSection(skills)}`;
|
|
153
|
+
const [skills, instructions] = await Promise.all([loadSkills(config), loadProjectInstructions(specPath)]);
|
|
154
|
+
const systemContext = buildSystemContext(skills, instructions);
|
|
35
155
|
const planSpecImpl = resolvePlanSpecImpl(config);
|
|
36
156
|
const result = await planSpecImpl(spec, systemContext, config);
|
|
37
|
-
|
|
38
|
-
|
|
157
|
+
try {
|
|
158
|
+
validateDAG(result.tasks);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
throw new InvalidPlanError("planner", error instanceof Error ? error.message : String(error));
|
|
162
|
+
}
|
|
163
|
+
let finalTasks = result.tasks;
|
|
164
|
+
try {
|
|
165
|
+
const reviewed = await runTaskGraphReview(result.tasks, systemContext, config);
|
|
166
|
+
finalTasks = reviewed.finalTasks;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (config?.review?.onInvalid === "warn_skip") {
|
|
170
|
+
logger.warn(`Review changes rejected; proceeding with planner graph (${error instanceof Error ? error.message : String(error)})`);
|
|
171
|
+
finalTasks = result.tasks;
|
|
172
|
+
}
|
|
173
|
+
else if (error instanceof InvalidPlanError) {
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
throw new InvalidPlanError("review", `Review stage failed. ${error instanceof Error ? error.message : String(error)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
await store.writeTasks(runId, finalTasks);
|
|
39
181
|
await store.updateRun(runId, {
|
|
40
182
|
overallStatus: "planning",
|
|
41
|
-
tasks:
|
|
183
|
+
tasks: finalTasks,
|
|
42
184
|
milestones: ["plan-complete"]
|
|
43
185
|
});
|
|
44
|
-
logger.success(`Plan complete: ${
|
|
186
|
+
logger.success(`Plan complete: ${finalTasks.length} tasks`);
|
|
45
187
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const TaskSchema = z.object({
|
|
3
|
+
id: z.string().min(1),
|
|
4
|
+
name: z.string().min(1),
|
|
5
|
+
description: z.string(),
|
|
6
|
+
dependencies: z.array(z.string()),
|
|
7
|
+
acceptance_criteria: z.array(z.string()),
|
|
8
|
+
status: z.enum(["pending", "in_progress", "done", "failed", "cancelled"]),
|
|
9
|
+
retries: z.number(),
|
|
10
|
+
maxRetries: z.number(),
|
|
11
|
+
startedAt: z.string().optional(),
|
|
12
|
+
finishedAt: z.string().optional(),
|
|
13
|
+
lastError: z.string().optional()
|
|
14
|
+
}).strict();
|
|
15
|
+
const ReviewOperationSchema = z.discriminatedUnion("op", [
|
|
16
|
+
z.object({
|
|
17
|
+
op: z.literal("update_task"),
|
|
18
|
+
taskId: z.string().min(1),
|
|
19
|
+
fields: z.object({
|
|
20
|
+
name: z.string().min(1).optional(),
|
|
21
|
+
description: z.string().optional(),
|
|
22
|
+
acceptance_criteria: z.array(z.string()).optional()
|
|
23
|
+
}).strict()
|
|
24
|
+
}).strict(),
|
|
25
|
+
z.object({
|
|
26
|
+
op: z.literal("add_task"),
|
|
27
|
+
task: TaskSchema
|
|
28
|
+
}).strict(),
|
|
29
|
+
z.object({
|
|
30
|
+
op: z.literal("remove_task"),
|
|
31
|
+
taskId: z.string().min(1)
|
|
32
|
+
}).strict(),
|
|
33
|
+
z.object({
|
|
34
|
+
op: z.literal("add_dependency"),
|
|
35
|
+
taskId: z.string().min(1),
|
|
36
|
+
dependsOn: z.string().min(1)
|
|
37
|
+
}).strict(),
|
|
38
|
+
z.object({
|
|
39
|
+
op: z.literal("remove_dependency"),
|
|
40
|
+
taskId: z.string().min(1),
|
|
41
|
+
dependsOn: z.string().min(1)
|
|
42
|
+
}).strict()
|
|
43
|
+
]);
|
|
44
|
+
export const TaskGraphReviewPayloadSchema = z.object({
|
|
45
|
+
changes: z.array(ReviewOperationSchema)
|
|
46
|
+
}).strict();
|
|
47
|
+
function findTaskIndex(tasks, taskId) {
|
|
48
|
+
return tasks.findIndex((task) => task.id === taskId);
|
|
49
|
+
}
|
|
50
|
+
export function summarizeReviewChanges(changes) {
|
|
51
|
+
return changes.map((change) => {
|
|
52
|
+
switch (change.op) {
|
|
53
|
+
case "update_task": {
|
|
54
|
+
const keys = Object.keys(change.fields);
|
|
55
|
+
return `update_task(${change.taskId}: ${keys.join(",") || "no fields"})`;
|
|
56
|
+
}
|
|
57
|
+
case "add_task":
|
|
58
|
+
return `add_task(${change.task.id})`;
|
|
59
|
+
case "remove_task":
|
|
60
|
+
return `remove_task(${change.taskId})`;
|
|
61
|
+
case "add_dependency":
|
|
62
|
+
return `add_dependency(${change.taskId}<-${change.dependsOn})`;
|
|
63
|
+
case "remove_dependency":
|
|
64
|
+
return `remove_dependency(${change.taskId}<-${change.dependsOn})`;
|
|
65
|
+
default:
|
|
66
|
+
return "unknown";
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export function applyTaskGraphReviewChanges(tasks, changes) {
|
|
71
|
+
const nextTasks = tasks.map((task) => ({ ...task, dependencies: [...task.dependencies], acceptance_criteria: [...task.acceptance_criteria] }));
|
|
72
|
+
for (const change of changes) {
|
|
73
|
+
switch (change.op) {
|
|
74
|
+
case "update_task": {
|
|
75
|
+
const index = findTaskIndex(nextTasks, change.taskId);
|
|
76
|
+
if (index === -1) {
|
|
77
|
+
throw new Error(`Review update_task failed: task not found (${change.taskId})`);
|
|
78
|
+
}
|
|
79
|
+
const current = nextTasks[index];
|
|
80
|
+
nextTasks[index] = {
|
|
81
|
+
...current,
|
|
82
|
+
...("name" in change.fields ? { name: change.fields.name ?? current.name } : {}),
|
|
83
|
+
...("description" in change.fields ? { description: change.fields.description ?? current.description } : {}),
|
|
84
|
+
...("acceptance_criteria" in change.fields
|
|
85
|
+
? { acceptance_criteria: [...(change.fields.acceptance_criteria ?? current.acceptance_criteria)] }
|
|
86
|
+
: {})
|
|
87
|
+
};
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "add_task": {
|
|
91
|
+
if (findTaskIndex(nextTasks, change.task.id) !== -1) {
|
|
92
|
+
throw new Error(`Review add_task failed: task already exists (${change.task.id})`);
|
|
93
|
+
}
|
|
94
|
+
nextTasks.push({
|
|
95
|
+
...change.task,
|
|
96
|
+
dependencies: [...change.task.dependencies],
|
|
97
|
+
acceptance_criteria: [...change.task.acceptance_criteria]
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "remove_task": {
|
|
102
|
+
const index = findTaskIndex(nextTasks, change.taskId);
|
|
103
|
+
if (index === -1) {
|
|
104
|
+
throw new Error(`Review remove_task failed: task not found (${change.taskId})`);
|
|
105
|
+
}
|
|
106
|
+
nextTasks.splice(index, 1);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "add_dependency": {
|
|
110
|
+
const index = findTaskIndex(nextTasks, change.taskId);
|
|
111
|
+
if (index === -1) {
|
|
112
|
+
throw new Error(`Review add_dependency failed: task not found (${change.taskId})`);
|
|
113
|
+
}
|
|
114
|
+
const current = nextTasks[index];
|
|
115
|
+
if (!current.dependencies.includes(change.dependsOn)) {
|
|
116
|
+
current.dependencies = [...current.dependencies, change.dependsOn];
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "remove_dependency": {
|
|
121
|
+
const index = findTaskIndex(nextTasks, change.taskId);
|
|
122
|
+
if (index === -1) {
|
|
123
|
+
throw new Error(`Review remove_dependency failed: task not found (${change.taskId})`);
|
|
124
|
+
}
|
|
125
|
+
const current = nextTasks[index];
|
|
126
|
+
current.dependencies = current.dependencies.filter((dependency) => dependency !== change.dependsOn);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return nextTasks;
|
|
132
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function createStdoutHookHandler(prefix = "[hook]") {
|
|
1
|
+
export function createStdoutHookHandler(prefix = "[hook]", write = console.log) {
|
|
2
2
|
return async (event) => {
|
|
3
3
|
const line = {
|
|
4
4
|
prefix,
|
|
@@ -10,6 +10,6 @@ export function createStdoutHookHandler(prefix = "[hook]") {
|
|
|
10
10
|
error: event.error,
|
|
11
11
|
metadata: event.metadata
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
write(JSON.stringify(line));
|
|
14
14
|
};
|
|
15
15
|
}
|
package/dist/hooks/dispatcher.js
CHANGED
|
@@ -64,7 +64,10 @@ export class HookDispatcher {
|
|
|
64
64
|
...process.env,
|
|
65
65
|
ORCA_MSG: event.message,
|
|
66
66
|
ORCA_RUN_ID: event.runId,
|
|
67
|
-
ORCA_TASK_ID: event.taskId ?? ""
|
|
67
|
+
ORCA_TASK_ID: event.taskId ?? "",
|
|
68
|
+
ORCA_HOOK: event.hook,
|
|
69
|
+
ORCA_ERROR: event.error ?? "",
|
|
70
|
+
ORCA_STAGE: typeof event.metadata?.stage === "string" ? event.metadata.stage : ""
|
|
68
71
|
}
|
|
69
72
|
});
|
|
70
73
|
}
|
package/dist/utils/agent-json.js
CHANGED
|
@@ -3,7 +3,7 @@ function tryParseJson(input) {
|
|
|
3
3
|
return JSON.parse(input);
|
|
4
4
|
}
|
|
5
5
|
catch {
|
|
6
|
-
return
|
|
6
|
+
return undefined;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
function extractFencedCandidates(text) {
|
|
@@ -54,7 +54,7 @@ function extractFirstJsonObjectOrArray(text) {
|
|
|
54
54
|
depth -= 1;
|
|
55
55
|
if (depth === 0) {
|
|
56
56
|
const candidate = text.slice(start, end + 1).trim();
|
|
57
|
-
if (tryParseJson(candidate) !==
|
|
57
|
+
if (tryParseJson(candidate) !== undefined) {
|
|
58
58
|
return candidate;
|
|
59
59
|
}
|
|
60
60
|
break;
|
|
@@ -76,7 +76,7 @@ export function parseAgentJson(raw) {
|
|
|
76
76
|
}
|
|
77
77
|
for (const candidate of candidates) {
|
|
78
78
|
const parsed = tryParseJson(candidate);
|
|
79
|
-
if (parsed !==
|
|
79
|
+
if (parsed !== undefined) {
|
|
80
80
|
return parsed;
|
|
81
81
|
}
|
|
82
82
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orcastrator",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"orca": "dist/cli/index.js"
|
|
@@ -9,11 +9,15 @@
|
|
|
9
9
|
"build": "tsc",
|
|
10
10
|
"dev": "bun run src/cli/index.ts",
|
|
11
11
|
"lint": "oxlint src/",
|
|
12
|
-
"typecheck": "
|
|
13
|
-
"test": "bun test",
|
|
12
|
+
"typecheck": "node ./scripts/typecheck.mjs",
|
|
13
|
+
"test": "bun test src",
|
|
14
14
|
"start": "bun run src/cli/index.ts",
|
|
15
15
|
"prepare": "husky",
|
|
16
|
-
"postbuild": "chmod +x dist/cli/index.js"
|
|
16
|
+
"postbuild": "chmod +x dist/cli/index.js",
|
|
17
|
+
"lint:type-aware": "oxlint --type-aware --type-check --deny-warnings src/ --ignore-pattern \"**/*.test.ts\"",
|
|
18
|
+
"typecheck:native": "tsgo --noEmit",
|
|
19
|
+
"typecheck:tsc": "tsc --noEmit",
|
|
20
|
+
"validate": "npm run lint && npm run lint:type-aware && npm run typecheck && npm run test && npm run build"
|
|
17
21
|
},
|
|
18
22
|
"dependencies": {
|
|
19
23
|
"@anthropic-ai/claude-agent-sdk": "^0.2.47",
|
|
@@ -21,14 +25,16 @@
|
|
|
21
25
|
"@ratley/codex-client": "^0.1.3",
|
|
22
26
|
"chalk": "^5.3.0",
|
|
23
27
|
"commander": "^13.1.0",
|
|
24
|
-
"zod": "^3.
|
|
28
|
+
"zod": "^4.3.6"
|
|
25
29
|
},
|
|
26
30
|
"devDependencies": {
|
|
27
31
|
"@types/bun": "^1.2.21",
|
|
28
32
|
"husky": "^9.1.7",
|
|
29
33
|
"lint-staged": "^16.2.0",
|
|
30
|
-
"oxlint": "^
|
|
31
|
-
"typescript": "^5.8.2"
|
|
34
|
+
"oxlint": "^1.49.0",
|
|
35
|
+
"typescript": "^5.8.2",
|
|
36
|
+
"@typescript/native-preview": "^7.0.0-dev.20260219.1",
|
|
37
|
+
"oxlint-tsgolint": "^0.14.1"
|
|
32
38
|
},
|
|
33
39
|
"lint-staged": {
|
|
34
40
|
"*.ts": [
|