@agentic-workflow-kit/orchestrator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/analysis/runAnalyzer.d.ts +22 -0
- package/dist/analysis/runAnalyzer.d.ts.map +1 -0
- package/dist/analysis/runAnalyzer.js +177 -0
- package/dist/artifacts/FileArtifactStore.d.ts +9 -0
- package/dist/artifacts/FileArtifactStore.d.ts.map +1 -0
- package/dist/artifacts/FileArtifactStore.js +21 -0
- package/dist/cli/args.d.ts +5 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +213 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +284 -0
- package/dist/clock/SystemClock.d.ts +6 -0
- package/dist/clock/SystemClock.d.ts.map +1 -0
- package/dist/clock/SystemClock.js +8 -0
- package/dist/config/configLoader.d.ts +5 -0
- package/dist/config/configLoader.d.ts.map +1 -0
- package/dist/config/configLoader.js +105 -0
- package/dist/config/generate-schema.d.ts +2 -0
- package/dist/config/generate-schema.d.ts.map +1 -0
- package/dist/config/generate-schema.js +8 -0
- package/dist/config/jsonSchema.d.ts +3 -0
- package/dist/config/jsonSchema.d.ts.map +1 -0
- package/dist/config/jsonSchema.js +44 -0
- package/dist/config/preset.d.ts +16 -0
- package/dist/config/preset.d.ts.map +1 -0
- package/dist/config/preset.js +14 -0
- package/dist/config/resolve.d.ts +12 -0
- package/dist/config/resolve.d.ts.map +1 -0
- package/dist/config/resolve.js +30 -0
- package/dist/config/schema.d.ts +68 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +80 -0
- package/dist/drivers/StoryRunner.d.ts +24 -0
- package/dist/drivers/StoryRunner.d.ts.map +1 -0
- package/dist/drivers/StoryRunner.js +1 -0
- package/dist/drivers/codex-mcp/CodexMcpStoryRunner.d.ts +25 -0
- package/dist/drivers/codex-mcp/CodexMcpStoryRunner.d.ts.map +1 -0
- package/dist/drivers/codex-mcp/CodexMcpStoryRunner.js +145 -0
- package/dist/drivers/codex-mcp/schemaValidation.d.ts +7 -0
- package/dist/drivers/codex-mcp/schemaValidation.d.ts.map +1 -0
- package/dist/drivers/codex-mcp/schemaValidation.js +43 -0
- package/dist/drivers/codex-mcp/toolInput.d.ts +12 -0
- package/dist/drivers/codex-mcp/toolInput.d.ts.map +1 -0
- package/dist/drivers/codex-mcp/toolInput.js +82 -0
- package/dist/git/GitInspector.d.ts +34 -0
- package/dist/git/GitInspector.d.ts.map +1 -0
- package/dist/git/GitInspector.js +73 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/internal/guards.d.ts +4 -0
- package/dist/internal/guards.d.ts.map +1 -0
- package/dist/internal/guards.js +9 -0
- package/dist/logging/ConsoleLogger.d.ts +7 -0
- package/dist/logging/ConsoleLogger.d.ts.map +1 -0
- package/dist/logging/ConsoleLogger.js +16 -0
- package/dist/metrics/aggregate.d.ts +6 -0
- package/dist/metrics/aggregate.d.ts.map +1 -0
- package/dist/metrics/aggregate.js +32 -0
- package/dist/metrics/liveMetrics.d.ts +16 -0
- package/dist/metrics/liveMetrics.d.ts.map +1 -0
- package/dist/metrics/liveMetrics.js +16 -0
- package/dist/runner/CompletionGate.d.ts +25 -0
- package/dist/runner/CompletionGate.d.ts.map +1 -0
- package/dist/runner/CompletionGate.js +49 -0
- package/dist/runner/MetricsCollector.d.ts +27 -0
- package/dist/runner/MetricsCollector.d.ts.map +1 -0
- package/dist/runner/MetricsCollector.js +49 -0
- package/dist/runner/RunJournal.d.ts +32 -0
- package/dist/runner/RunJournal.d.ts.map +1 -0
- package/dist/runner/RunJournal.js +78 -0
- package/dist/runner/WorkflowRunner.d.ts +45 -0
- package/dist/runner/WorkflowRunner.d.ts.map +1 -0
- package/dist/runner/WorkflowRunner.js +289 -0
- package/dist/scheduler/scheduler.d.ts +8 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +8 -0
- package/dist/tracks/markdownTracker.d.ts +29 -0
- package/dist/tracks/markdownTracker.d.ts.map +1 -0
- package/dist/tracks/markdownTracker.js +349 -0
- package/dist/types.d.ts +222 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +75 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { analyzeWorkflowRun } from './analysis/runAnalyzer.js';
|
|
8
|
+
import { FileArtifactStore } from './artifacts/FileArtifactStore.js';
|
|
9
|
+
import { getHelpText, parseCommand, resolveInvocationCwd } from './cli/args.js';
|
|
10
|
+
import { SystemClock } from './clock/SystemClock.js';
|
|
11
|
+
import { createRunId, loadResolvedConfig, resolveCwdOnlyConfig } from './config/configLoader.js';
|
|
12
|
+
import { CodexMcpStoryRunner } from './drivers/codex-mcp/CodexMcpStoryRunner.js';
|
|
13
|
+
import { RealGitInspector } from './git/GitInspector.js';
|
|
14
|
+
import { ConsoleLogger } from './logging/ConsoleLogger.js';
|
|
15
|
+
import { WorkflowRunner } from './runner/WorkflowRunner.js';
|
|
16
|
+
import { selectDispatchableStories } from './scheduler/scheduler.js';
|
|
17
|
+
import { discoverMarkdownTracks, EmptyStorySource, MarkdownTrackStorySource } from './tracks/markdownTracker.js';
|
|
18
|
+
export async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
19
|
+
const command = parseCommand(argv);
|
|
20
|
+
const logger = new ConsoleLogger();
|
|
21
|
+
const stdout = options.stdout ?? console.log;
|
|
22
|
+
if (command.kind === 'help') {
|
|
23
|
+
stdout(getHelpText());
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (command.kind === 'analyze-run') {
|
|
27
|
+
const analysis = await analyzeWorkflowRun(path.resolve(command.runPath), {
|
|
28
|
+
sessionRoots: command.overrides.sessionRoot ? [path.resolve(command.overrides.sessionRoot)] : undefined,
|
|
29
|
+
});
|
|
30
|
+
stdout(JSON.stringify(analysis, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (command.kind === 'watch-run') {
|
|
34
|
+
await printRunSnapshot(path.resolve(command.runPath), command.overrides, stdout);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const cwd = resolveInvocationCwd(command.overrides);
|
|
38
|
+
if (command.kind === 'mcp-check') {
|
|
39
|
+
const storyRunner = new CodexMcpStoryRunner(resolveCwdOnlyConfig(cwd), {
|
|
40
|
+
...(options.createCodexMcpClient ? { createClient: options.createCodexMcpClient } : {}),
|
|
41
|
+
});
|
|
42
|
+
const status = await storyRunner.checkTools();
|
|
43
|
+
if (command.overrides.json) {
|
|
44
|
+
stdout(JSON.stringify(status, null, 2));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
logger.info('Codex MCP schema check passed', { tools: status.tools });
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const config = await loadResolvedConfig(command.overrides, cwd);
|
|
52
|
+
const tracks = await discoverTracks(config, command.overrides);
|
|
53
|
+
if (command.kind === 'list-tracks') {
|
|
54
|
+
printTracks(tracks, command.overrides);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (command.kind === 'list-stories') {
|
|
58
|
+
const stories = command.overrides.track
|
|
59
|
+
? selectTrack(tracks, command.overrides.track).stories
|
|
60
|
+
: tracks.flatMap((track) => track.stories);
|
|
61
|
+
printStories(stories, command.overrides, 'No stories found.');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (command.kind === 'list-eligible') {
|
|
65
|
+
const stories = command.overrides.track
|
|
66
|
+
? selectTrack(tracks, command.overrides.track).stories
|
|
67
|
+
: tracks.flatMap((track) => track.stories);
|
|
68
|
+
const eligible = stories.filter((story) => story.eligible);
|
|
69
|
+
printStories(eligible, command.overrides, 'No eligible stories.');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const runId = createRunId();
|
|
73
|
+
const runDirectory = path.join(config.artifacts.runsDirAbs, runId);
|
|
74
|
+
const storySource = command.kind === 'run-story'
|
|
75
|
+
? selectStorySourceForStory(config, tracks, command.storyId, command.overrides)
|
|
76
|
+
: selectStorySourceForRunEligible(config, tracks, command.overrides);
|
|
77
|
+
const workflowRunner = new WorkflowRunner({
|
|
78
|
+
command: command.kind,
|
|
79
|
+
config,
|
|
80
|
+
storySource,
|
|
81
|
+
storyRunner: new CodexMcpStoryRunner(config),
|
|
82
|
+
gitInspector: new RealGitInspector(),
|
|
83
|
+
artifactStore: new FileArtifactStore(runDirectory),
|
|
84
|
+
logger,
|
|
85
|
+
clock: new SystemClock(),
|
|
86
|
+
runId,
|
|
87
|
+
});
|
|
88
|
+
const run = command.kind === 'run-story'
|
|
89
|
+
? workflowRunner.runStory(command.storyId, { force: command.overrides.force })
|
|
90
|
+
: command.overrides.dryRun
|
|
91
|
+
? workflowRunner.dryRunEligible()
|
|
92
|
+
: workflowRunner.runEligible();
|
|
93
|
+
const result = command.overrides.watch ? await runWithEventWatch(run, runDirectory, command.overrides) : await run;
|
|
94
|
+
if (command.overrides.json) {
|
|
95
|
+
console.log(JSON.stringify(result, null, 2));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
logger.info('run finished', {
|
|
99
|
+
runId: result.runId,
|
|
100
|
+
status: result.status,
|
|
101
|
+
completed: result.completed.map((child) => child.storyId),
|
|
102
|
+
blockedStoryId: result.blockedStoryId,
|
|
103
|
+
artifacts: runDirectory,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (result.status === 'blocked') {
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function discoverTracks(config, overrides) {
|
|
111
|
+
return await discoverMarkdownTracks({
|
|
112
|
+
workspaceRoot: config.workspace.rootAbs,
|
|
113
|
+
tracksDir: overrides.tracksDir ?? config.paths.tracksDir,
|
|
114
|
+
archiveDir: config.paths.archiveDir,
|
|
115
|
+
completeStatuses: config.statuses.complete,
|
|
116
|
+
eligibleStatuses: config.statuses.eligible,
|
|
117
|
+
idPattern: config.tracker.idPattern,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function selectStorySourceForRunEligible(config, tracks, overrides) {
|
|
121
|
+
if (overrides.track) {
|
|
122
|
+
const track = selectTrack(tracks, overrides.track);
|
|
123
|
+
return new MarkdownTrackStorySource(trackOptions(config, overrides), track.id);
|
|
124
|
+
}
|
|
125
|
+
const eligibleTracks = tracks.filter((track) => track.stories.some((story) => story.eligible));
|
|
126
|
+
if (eligibleTracks.length === 0)
|
|
127
|
+
return new EmptyStorySource();
|
|
128
|
+
if (eligibleTracks.length > 1) {
|
|
129
|
+
throw new Error(`multiple tracks have eligible stories: ${eligibleTracks.map((track) => track.id).join(', ')}; pass --track <id>`);
|
|
130
|
+
}
|
|
131
|
+
return new MarkdownTrackStorySource(trackOptions(config, overrides), eligibleTracks[0].id);
|
|
132
|
+
}
|
|
133
|
+
function selectStorySourceForStory(config, tracks, storyId, overrides) {
|
|
134
|
+
if (overrides.track) {
|
|
135
|
+
const track = selectTrack(tracks, overrides.track);
|
|
136
|
+
return new MarkdownTrackStorySource(trackOptions(config, overrides), track.id);
|
|
137
|
+
}
|
|
138
|
+
const matchingTracks = tracks.filter((track) => track.stories.some((story) => story.id === storyId));
|
|
139
|
+
if (matchingTracks.length === 0)
|
|
140
|
+
return new EmptyStorySource();
|
|
141
|
+
if (matchingTracks.length > 1) {
|
|
142
|
+
throw new Error(`story ${storyId} exists in multiple tracks: ${matchingTracks.map((track) => track.id).join(', ')}; pass --track <id>`);
|
|
143
|
+
}
|
|
144
|
+
return new MarkdownTrackStorySource(trackOptions(config, overrides), matchingTracks[0].id);
|
|
145
|
+
}
|
|
146
|
+
function selectTrack(tracks, trackId) {
|
|
147
|
+
const track = tracks.find((entry) => entry.id === trackId);
|
|
148
|
+
if (!track)
|
|
149
|
+
throw new Error(`track ${trackId} was not found`);
|
|
150
|
+
return track;
|
|
151
|
+
}
|
|
152
|
+
function trackOptions(config, overrides) {
|
|
153
|
+
return {
|
|
154
|
+
workspaceRoot: config.workspace.rootAbs,
|
|
155
|
+
tracksDir: overrides.tracksDir ?? config.paths.tracksDir,
|
|
156
|
+
archiveDir: config.paths.archiveDir,
|
|
157
|
+
completeStatuses: config.statuses.complete,
|
|
158
|
+
eligibleStatuses: config.statuses.eligible,
|
|
159
|
+
idPattern: config.tracker.idPattern,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function printTracks(tracks, overrides) {
|
|
163
|
+
if (overrides.json) {
|
|
164
|
+
console.log(JSON.stringify(tracks, null, 2));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (tracks.length === 0) {
|
|
168
|
+
console.log('No tracks found.');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
for (const track of tracks) {
|
|
172
|
+
const eligibleCount = track.stories.filter((story) => story.eligible).length;
|
|
173
|
+
console.log(`${track.id} ${track.title} (${eligibleCount}/${track.stories.length} eligible)`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function printStories(stories, overrides, emptyMessage) {
|
|
177
|
+
const limited = selectDispatchableStories(stories, {
|
|
178
|
+
maxParallel: overrides.maxParallel ?? stories.length,
|
|
179
|
+
});
|
|
180
|
+
const printable = overrides.maxParallel ? limited : stories;
|
|
181
|
+
if (overrides.json) {
|
|
182
|
+
console.log(JSON.stringify(printable, null, 2));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (printable.length === 0) {
|
|
186
|
+
console.log(emptyMessage);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (const story of printable) {
|
|
190
|
+
const title = story.title ? ` ${story.title}` : '';
|
|
191
|
+
console.log(`${story.id}${title} [${story.status}]`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function printRunSnapshot(runDirectory, overrides, stdout) {
|
|
195
|
+
const [state, metrics] = await Promise.all([
|
|
196
|
+
readJsonIfExists(path.join(runDirectory, 'state.json')),
|
|
197
|
+
readJsonIfExists(path.join(runDirectory, 'metrics.live.json')),
|
|
198
|
+
]);
|
|
199
|
+
const snapshot = { state, metrics };
|
|
200
|
+
if (overrides.json) {
|
|
201
|
+
stdout(JSON.stringify(snapshot, null, 2));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
stdout(`Run: ${runDirectory}`);
|
|
205
|
+
stdout(JSON.stringify(snapshot, null, 2));
|
|
206
|
+
}
|
|
207
|
+
async function runWithEventWatch(run, runDirectory, overrides) {
|
|
208
|
+
let done = false;
|
|
209
|
+
const watch = watchRunEvents(runDirectory, overrides, () => done);
|
|
210
|
+
try {
|
|
211
|
+
const result = await run;
|
|
212
|
+
done = true;
|
|
213
|
+
await watch;
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
done = true;
|
|
218
|
+
await watch;
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function watchRunEvents(runDirectory, overrides, isDone) {
|
|
223
|
+
const eventPath = path.join(runDirectory, 'events.ndjson');
|
|
224
|
+
let printed = 0;
|
|
225
|
+
while (!isDone()) {
|
|
226
|
+
printed = await printNewEvents(eventPath, printed, overrides);
|
|
227
|
+
await delay(250);
|
|
228
|
+
}
|
|
229
|
+
await printNewEvents(eventPath, printed, overrides);
|
|
230
|
+
}
|
|
231
|
+
async function printNewEvents(eventPath, printed, overrides) {
|
|
232
|
+
let content;
|
|
233
|
+
try {
|
|
234
|
+
content = await readFile(eventPath, 'utf8');
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
238
|
+
return printed;
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
const lines = content.trimEnd().split('\n').filter(Boolean);
|
|
242
|
+
for (const line of lines.slice(printed)) {
|
|
243
|
+
if (overrides.json) {
|
|
244
|
+
console.log(line);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
const event = JSON.parse(line);
|
|
248
|
+
const story = typeof event.storyId === 'string' ? ` ${event.storyId}` : '';
|
|
249
|
+
const status = typeof event.status === 'string' ? ` ${event.status}` : '';
|
|
250
|
+
console.log(`${String(event.ts ?? '')} ${String(event.type ?? 'event')}${story}${status}`.trim());
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return lines.length;
|
|
254
|
+
}
|
|
255
|
+
function delay(ms) {
|
|
256
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
257
|
+
}
|
|
258
|
+
async function readJsonIfExists(filePath) {
|
|
259
|
+
try {
|
|
260
|
+
return JSON.parse(await readFile(filePath, 'utf8'));
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
264
|
+
return null;
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (isDirectCliExecution()) {
|
|
269
|
+
runCli().catch((error) => {
|
|
270
|
+
const logger = new ConsoleLogger();
|
|
271
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
272
|
+
process.exitCode = 1;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
export function isDirectCliExecution(entrypoint = process.argv[1], moduleUrl = import.meta.url) {
|
|
276
|
+
if (entrypoint === undefined)
|
|
277
|
+
return false;
|
|
278
|
+
try {
|
|
279
|
+
return realpathSync(entrypoint) === realpathSync(fileURLToPath(moduleUrl));
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SystemClock.d.ts","sourceRoot":"","sources":["../../src/clock/SystemClock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,qBAAa,WAAY,YAAW,KAAK;IACvC,GAAG,IAAI,MAAM;IAGb,KAAK,IAAI,MAAM;CAGhB"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CliOverrides, ResolvedWorkflowConfig } from '../types.js';
|
|
2
|
+
export declare function loadResolvedConfig(overrides?: CliOverrides, cwd?: string): Promise<ResolvedWorkflowConfig>;
|
|
3
|
+
export declare function resolveCwdOnlyConfig(cwd?: string): ResolvedWorkflowConfig;
|
|
4
|
+
export declare function createRunId(now?: () => string): string;
|
|
5
|
+
//# sourceMappingURL=configLoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configLoader.d.ts","sourceRoot":"","sources":["../../src/config/configLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAsB,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAK5F,wBAAsB,kBAAkB,CACtC,SAAS,GAAE,YAAiB,EAC5B,GAAG,SAAgB,GAClB,OAAO,CAAC,sBAAsB,CAAC,CAsDjC;AAED,wBAAgB,oBAAoB,CAAC,GAAG,SAAgB,GAAG,sBAAsB,CAuChF;AAED,wBAAgB,WAAW,CAAC,GAAG,eAAiC,GAAG,MAAM,CAExE"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { loadConfig } from './resolve.js';
|
|
3
|
+
const SUPPORTED_DRIVERS = new Set(['codex-mcp']);
|
|
4
|
+
export async function loadResolvedConfig(overrides = {}, cwd = process.cwd()) {
|
|
5
|
+
const { config, workspaceRoot, configPath } = await loadConfig({
|
|
6
|
+
cwd,
|
|
7
|
+
configPath: overrides.configPath,
|
|
8
|
+
});
|
|
9
|
+
const driver = resolveDriver(config.orchestrator.driver);
|
|
10
|
+
const tracksDir = overrides.tracksDir ?? config.paths.tracksDir;
|
|
11
|
+
const archiveDir = config.paths.archiveDir;
|
|
12
|
+
const maxParallel = overrides.maxParallel ?? config.orchestrator.maxParallel;
|
|
13
|
+
const childTimeoutMs = overrides.childTimeoutMs ?? config.orchestrator.childTimeoutMs;
|
|
14
|
+
const childSessionConfig = {};
|
|
15
|
+
if (overrides.reasoning !== undefined) {
|
|
16
|
+
childSessionConfig.model_reasoning_effort = overrides.reasoning;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
version: 1,
|
|
20
|
+
configPath,
|
|
21
|
+
workspace: { rootAbs: workspaceRoot },
|
|
22
|
+
paths: {
|
|
23
|
+
tracksDir,
|
|
24
|
+
tracksDirAbs: path.resolve(workspaceRoot, tracksDir),
|
|
25
|
+
archiveDir,
|
|
26
|
+
archiveDirAbs: path.resolve(workspaceRoot, archiveDir),
|
|
27
|
+
},
|
|
28
|
+
artifacts: {
|
|
29
|
+
rootDir: '.codex/agentic-workflow-kit',
|
|
30
|
+
rootDirAbs: path.resolve(workspaceRoot, '.codex/agentic-workflow-kit'),
|
|
31
|
+
runsDirAbs: path.resolve(workspaceRoot, '.codex/agentic-workflow-kit/runs'),
|
|
32
|
+
},
|
|
33
|
+
statuses: {
|
|
34
|
+
eligible: config.statuses.eligible,
|
|
35
|
+
inProgress: config.statuses.inProgress,
|
|
36
|
+
complete: config.statuses.complete,
|
|
37
|
+
},
|
|
38
|
+
tracker: { idPattern: config.tracker.idPattern },
|
|
39
|
+
git: config.git,
|
|
40
|
+
orchestrator: {
|
|
41
|
+
driver,
|
|
42
|
+
maxParallel,
|
|
43
|
+
stopLaunchingOnBlocked: config.orchestrator.stopLaunchingOnBlocked,
|
|
44
|
+
childTimeoutMs,
|
|
45
|
+
},
|
|
46
|
+
codex: {
|
|
47
|
+
childSession: {
|
|
48
|
+
cwdAbs: workspaceRoot,
|
|
49
|
+
...(overrides.model !== undefined ? { model: overrides.model } : {}),
|
|
50
|
+
...(overrides.approvalPolicy !== undefined ? { approvalPolicy: overrides.approvalPolicy } : {}),
|
|
51
|
+
...(overrides.sandbox !== undefined ? { sandbox: overrides.sandbox } : {}),
|
|
52
|
+
...(Object.keys(childSessionConfig).length > 0 ? { config: childSessionConfig } : {}),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function resolveCwdOnlyConfig(cwd = process.cwd()) {
|
|
58
|
+
const workspaceRoot = path.resolve(cwd);
|
|
59
|
+
return {
|
|
60
|
+
version: 1,
|
|
61
|
+
configPath: path.resolve(workspaceRoot, '.workflow/config.yaml'),
|
|
62
|
+
workspace: { rootAbs: workspaceRoot },
|
|
63
|
+
paths: {
|
|
64
|
+
tracksDir: 'docs/tracks',
|
|
65
|
+
tracksDirAbs: path.resolve(workspaceRoot, 'docs/tracks'),
|
|
66
|
+
archiveDir: 'docs/tracks/archive',
|
|
67
|
+
archiveDirAbs: path.resolve(workspaceRoot, 'docs/tracks/archive'),
|
|
68
|
+
},
|
|
69
|
+
artifacts: {
|
|
70
|
+
rootDir: '.codex/agentic-workflow-kit',
|
|
71
|
+
rootDirAbs: path.resolve(workspaceRoot, '.codex/agentic-workflow-kit'),
|
|
72
|
+
runsDirAbs: path.resolve(workspaceRoot, '.codex/agentic-workflow-kit/runs'),
|
|
73
|
+
},
|
|
74
|
+
statuses: {
|
|
75
|
+
eligible: ['specced', 'plan-approved'],
|
|
76
|
+
inProgress: 'implementing',
|
|
77
|
+
complete: ['done', 'verified'],
|
|
78
|
+
},
|
|
79
|
+
tracker: { idPattern: '^[A-Z]{2,}[0-9]+$' },
|
|
80
|
+
git: {
|
|
81
|
+
strategy: 'worktree',
|
|
82
|
+
branchPattern: '{track}/{id-lc}-{slug}',
|
|
83
|
+
baseBranch: 'main',
|
|
84
|
+
commitOnBase: 'forbid',
|
|
85
|
+
},
|
|
86
|
+
orchestrator: {
|
|
87
|
+
driver: 'codex-mcp',
|
|
88
|
+
maxParallel: 2,
|
|
89
|
+
stopLaunchingOnBlocked: true,
|
|
90
|
+
childTimeoutMs: 1_800_000,
|
|
91
|
+
},
|
|
92
|
+
codex: {
|
|
93
|
+
childSession: { cwdAbs: workspaceRoot },
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function createRunId(now = () => new Date().toISOString()) {
|
|
98
|
+
return now().replace(/[:.]/g, '-');
|
|
99
|
+
}
|
|
100
|
+
function resolveDriver(value) {
|
|
101
|
+
if (!SUPPORTED_DRIVERS.has(value)) {
|
|
102
|
+
throw new Error(`Unsupported orchestrator.driver "${value}". Supported drivers: codex-mcp.`);
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-schema.d.ts","sourceRoot":"","sources":["../../src/config/generate-schema.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { serializeConfigJsonSchema } from './jsonSchema.js';
|
|
5
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const target = path.resolve(here, '../../../../references/config.schema.json');
|
|
7
|
+
writeFileSync(target, serializeConfigJsonSchema());
|
|
8
|
+
console.error(`wrote ${target}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonSchema.d.ts","sourceRoot":"","sources":["../../src/config/jsonSchema.ts"],"names":[],"mappings":"AAMA,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ/D;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { isRecord } from '../internal/guards.js';
|
|
3
|
+
import { ConfigSchema } from './schema.js';
|
|
4
|
+
const ID = 'https://github.com/aryeko/agentic-workflow-kit/config.schema.json';
|
|
5
|
+
export function buildConfigJsonSchema() {
|
|
6
|
+
const generated = z.toJSONSchema(ConfigSchema);
|
|
7
|
+
return relaxObjectRequired({
|
|
8
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
9
|
+
$id: ID,
|
|
10
|
+
title: 'agentic-workflow-kit config',
|
|
11
|
+
...generated,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function serializeConfigJsonSchema() {
|
|
15
|
+
return `${JSON.stringify(buildConfigJsonSchema(), null, 2)}\n`;
|
|
16
|
+
}
|
|
17
|
+
function relaxObjectRequired(value) {
|
|
18
|
+
const relaxed = relaxObjectRequiredInner(value, true);
|
|
19
|
+
if (!isRecord(relaxed)) {
|
|
20
|
+
throw new Error('Expected generated config JSON Schema to be an object');
|
|
21
|
+
}
|
|
22
|
+
return relaxed;
|
|
23
|
+
}
|
|
24
|
+
function relaxObjectRequiredInner(value, isRoot) {
|
|
25
|
+
if (Array.isArray(value)) {
|
|
26
|
+
return value.map((entry) => relaxObjectRequiredInner(entry, false));
|
|
27
|
+
}
|
|
28
|
+
if (!isRecord(value)) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
const next = {};
|
|
32
|
+
for (const [key, child] of Object.entries(value)) {
|
|
33
|
+
next[key] = relaxObjectRequiredInner(child, false);
|
|
34
|
+
}
|
|
35
|
+
if (next.type === 'object') {
|
|
36
|
+
if (isRoot) {
|
|
37
|
+
next.required = ['version'];
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
delete next.required;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type PresetName = 'push-and-merge' | 'gated-automerge' | 'push-only';
|
|
2
|
+
export interface RepoSignals {
|
|
3
|
+
/** True when the base branch requires pull-request reviews before merge. */
|
|
4
|
+
requiresReview: boolean;
|
|
5
|
+
/** True when the repo has CI configured (e.g. .github/workflows present). */
|
|
6
|
+
hasCI: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Choose the default PR/merge preset from detected repo signals.
|
|
10
|
+
*
|
|
11
|
+
* - Required reviews => humans gate the merge => open a PR and stop (push-only).
|
|
12
|
+
* - CI but no required reviews => wait on CI + bot review, then auto-merge (gated-automerge).
|
|
13
|
+
* - Neither => open a PR and auto-merge after best-effort local checks (push-and-merge).
|
|
14
|
+
*/
|
|
15
|
+
export declare function selectPreset(signals: RepoSignals): PresetName;
|
|
16
|
+
//# sourceMappingURL=preset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../src/config/preset.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAE5E,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,cAAc,EAAE,OAAO,CAAC;IACxB,6EAA6E;IAC7E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAI7D"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choose the default PR/merge preset from detected repo signals.
|
|
3
|
+
*
|
|
4
|
+
* - Required reviews => humans gate the merge => open a PR and stop (push-only).
|
|
5
|
+
* - CI but no required reviews => wait on CI + bot review, then auto-merge (gated-automerge).
|
|
6
|
+
* - Neither => open a PR and auto-merge after best-effort local checks (push-and-merge).
|
|
7
|
+
*/
|
|
8
|
+
export function selectPreset(signals) {
|
|
9
|
+
if (signals.requiresReview)
|
|
10
|
+
return 'push-only';
|
|
11
|
+
if (signals.hasCI)
|
|
12
|
+
return 'gated-automerge';
|
|
13
|
+
return 'push-and-merge';
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type WorkflowConfig } from './schema.js';
|
|
2
|
+
export interface LoadConfigOptions {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
configPath?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface LoadedConfig {
|
|
7
|
+
configPath: string;
|
|
8
|
+
workspaceRoot: string;
|
|
9
|
+
config: WorkflowConfig;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadConfig(options?: LoadConfigOptions): Promise<LoadedConfig>;
|
|
12
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/config/resolve.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAIhE,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,wBAAsB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAsBvF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import YAML from 'yaml';
|
|
4
|
+
import { isNodeError } from '../internal/guards.js';
|
|
5
|
+
import { ConfigSchema } from './schema.js';
|
|
6
|
+
const DEFAULT_CONFIG_PATH = '.workflow/config.yaml';
|
|
7
|
+
export async function loadConfig(options = {}) {
|
|
8
|
+
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
|
|
9
|
+
const configPath = path.resolve(cwd, options.configPath ?? DEFAULT_CONFIG_PATH);
|
|
10
|
+
let raw;
|
|
11
|
+
try {
|
|
12
|
+
raw = await readFile(configPath, 'utf8');
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
16
|
+
throw new Error('Missing .workflow/config.yaml. Run /workflow-init before using agentic-workflow-kit orchestrator.');
|
|
17
|
+
}
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
const parsed = YAML.parse(raw);
|
|
21
|
+
const result = ConfigSchema.safeParse(parsed);
|
|
22
|
+
if (!result.success) {
|
|
23
|
+
throw new Error(formatZodError(result.error));
|
|
24
|
+
}
|
|
25
|
+
return { configPath, workspaceRoot: cwd, config: result.data };
|
|
26
|
+
}
|
|
27
|
+
function formatZodError(error) {
|
|
28
|
+
const details = error.issues.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`).join('; ');
|
|
29
|
+
return `Invalid .workflow/config.yaml — ${details}`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const ConfigSchema: z.ZodObject<{
|
|
3
|
+
version: z.ZodLiteral<1>;
|
|
4
|
+
paths: z.ZodPrefault<z.ZodObject<{
|
|
5
|
+
tracksDir: z.ZodDefault<z.ZodString>;
|
|
6
|
+
specsDir: z.ZodDefault<z.ZodString>;
|
|
7
|
+
plansDir: z.ZodDefault<z.ZodString>;
|
|
8
|
+
archiveDir: z.ZodDefault<z.ZodString>;
|
|
9
|
+
prdsDir: z.ZodDefault<z.ZodString>;
|
|
10
|
+
}, z.core.$strict>>;
|
|
11
|
+
statuses: z.ZodPrefault<z.ZodObject<{
|
|
12
|
+
eligible: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
13
|
+
inProgress: z.ZodDefault<z.ZodString>;
|
|
14
|
+
complete: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
15
|
+
}, z.core.$strict>>;
|
|
16
|
+
tracker: z.ZodPrefault<z.ZodObject<{
|
|
17
|
+
idPattern: z.ZodDefault<z.ZodString>;
|
|
18
|
+
}, z.core.$strict>>;
|
|
19
|
+
verify: z.ZodPrefault<z.ZodObject<{
|
|
20
|
+
changed: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
21
|
+
full: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
22
|
+
}, z.core.$strict>>;
|
|
23
|
+
git: z.ZodPrefault<z.ZodObject<{
|
|
24
|
+
strategy: z.ZodDefault<z.ZodEnum<{
|
|
25
|
+
worktree: "worktree";
|
|
26
|
+
branch: "branch";
|
|
27
|
+
}>>;
|
|
28
|
+
branchPattern: z.ZodDefault<z.ZodString>;
|
|
29
|
+
baseBranch: z.ZodDefault<z.ZodString>;
|
|
30
|
+
commitOnBase: z.ZodDefault<z.ZodEnum<{
|
|
31
|
+
forbid: "forbid";
|
|
32
|
+
allow: "allow";
|
|
33
|
+
}>>;
|
|
34
|
+
}, z.core.$strict>>;
|
|
35
|
+
pr: z.ZodPrefault<z.ZodObject<{
|
|
36
|
+
create: z.ZodDefault<z.ZodBoolean>;
|
|
37
|
+
ci: z.ZodPrefault<z.ZodObject<{
|
|
38
|
+
wait: z.ZodDefault<z.ZodBoolean>;
|
|
39
|
+
command: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
40
|
+
}, z.core.$strict>>;
|
|
41
|
+
review: z.ZodPrefault<z.ZodObject<{
|
|
42
|
+
wait: z.ZodDefault<z.ZodEnum<{
|
|
43
|
+
none: "none";
|
|
44
|
+
bot: "bot";
|
|
45
|
+
human: "human";
|
|
46
|
+
}>>;
|
|
47
|
+
bot: z.ZodDefault<z.ZodString>;
|
|
48
|
+
triageComments: z.ZodDefault<z.ZodBoolean>;
|
|
49
|
+
}, z.core.$strict>>;
|
|
50
|
+
merge: z.ZodPrefault<z.ZodObject<{
|
|
51
|
+
auto: z.ZodDefault<z.ZodBoolean>;
|
|
52
|
+
method: z.ZodDefault<z.ZodEnum<{
|
|
53
|
+
squash: "squash";
|
|
54
|
+
merge: "merge";
|
|
55
|
+
rebase: "rebase";
|
|
56
|
+
}>>;
|
|
57
|
+
deleteBranch: z.ZodDefault<z.ZodBoolean>;
|
|
58
|
+
}, z.core.$strict>>;
|
|
59
|
+
}, z.core.$strict>>;
|
|
60
|
+
orchestrator: z.ZodPrefault<z.ZodObject<{
|
|
61
|
+
driver: z.ZodDefault<z.ZodString>;
|
|
62
|
+
maxParallel: z.ZodDefault<z.ZodNumber>;
|
|
63
|
+
stopLaunchingOnBlocked: z.ZodDefault<z.ZodBoolean>;
|
|
64
|
+
childTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
65
|
+
}, z.core.$strict>>;
|
|
66
|
+
}, z.core.$strict>;
|
|
67
|
+
export type WorkflowConfig = z.infer<typeof ConfigSchema>;
|
|
68
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA6Ed,CAAC;AAEZ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC"}
|