agentic-dev 0.2.13 → 0.2.14
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 +7 -4
- package/lib/orchestration-assets.mjs +215 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,9 +55,10 @@ CLI는 이 순서로 동작한다.
|
|
|
55
55
|
5. `.agentic-dev/orchestration.json` / `.agentic-dev/setup.json` write
|
|
56
56
|
6. `.github/workflows/agentic-orchestration.yml` install
|
|
57
57
|
7. `.agentic-dev/runtime/*.mjs` install
|
|
58
|
-
8. `.
|
|
59
|
-
9.
|
|
60
|
-
10. `
|
|
58
|
+
8. `.agentic-dev/runtime/server.mjs` install
|
|
59
|
+
9. `.env.example -> .env`
|
|
60
|
+
10. `pnpm install`
|
|
61
|
+
11. `app mode != backend`이면 Playwright/bootstrap
|
|
61
62
|
|
|
62
63
|
## Workflow Contract
|
|
63
64
|
|
|
@@ -67,10 +68,12 @@ CLI는 이 순서로 동작한다.
|
|
|
67
68
|
- `.agentic-dev/runtime/sdd_to_ir.mjs`
|
|
68
69
|
- `.agentic-dev/runtime/sync_project_tasks.mjs`
|
|
69
70
|
- `.agentic-dev/runtime/run_multi_agent_queue.mjs`
|
|
71
|
+
- `.agentic-dev/runtime/dispatch_agents.mjs`
|
|
70
72
|
- `.agentic-dev/runtime/close_completed_tasks.mjs`
|
|
73
|
+
- `.agentic-dev/runtime/server.mjs`
|
|
71
74
|
- `.github/workflows/agentic-orchestration.yml`
|
|
72
75
|
|
|
73
|
-
즉 SDD planning 산출물이 push되면 workflow가 task IR
|
|
76
|
+
즉 SDD planning 산출물이 push되면 workflow가 local orchestration server를 띄우고, 그 서버가 task IR, GitHub task mirror, multi-agent queue, dispatch plan, close pass를 처리하는 구조다.
|
|
74
77
|
|
|
75
78
|
## Non-interactive Example
|
|
76
79
|
|
|
@@ -60,23 +60,100 @@ jobs:
|
|
|
60
60
|
with:
|
|
61
61
|
node-version: "20"
|
|
62
62
|
|
|
63
|
+
- name: Start orchestration server
|
|
64
|
+
env:
|
|
65
|
+
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
66
|
+
run: |
|
|
67
|
+
node .agentic-dev/runtime/server.mjs > /tmp/agentic-orchestration.log 2>&1 &
|
|
68
|
+
echo $! > /tmp/agentic-orchestration.pid
|
|
69
|
+
for i in $(seq 1 30); do
|
|
70
|
+
if curl -fsS http://127.0.0.1:4310/health >/dev/null; then
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
sleep 1
|
|
74
|
+
done
|
|
75
|
+
cat /tmp/agentic-orchestration.log
|
|
76
|
+
exit 1
|
|
77
|
+
|
|
63
78
|
- name: Build task IR from SDD planning
|
|
64
|
-
run:
|
|
79
|
+
run: curl -fsS -X POST http://127.0.0.1:4310/sync/ir
|
|
65
80
|
|
|
66
81
|
- name: Sync GitHub project tasks
|
|
67
82
|
env:
|
|
68
83
|
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
69
|
-
run:
|
|
84
|
+
run: curl -fsS -X POST http://127.0.0.1:4310/sync/tasks
|
|
70
85
|
|
|
71
86
|
- name: Plan multi-agent queue
|
|
72
87
|
env:
|
|
73
88
|
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
74
|
-
run:
|
|
89
|
+
run: curl -fsS -X POST http://127.0.0.1:4310/queue/plan
|
|
90
|
+
|
|
91
|
+
- name: Build dispatch plan
|
|
92
|
+
env:
|
|
93
|
+
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
94
|
+
run: curl -fsS -X POST http://127.0.0.1:4310/queue/dispatch
|
|
75
95
|
|
|
76
96
|
- name: Close completed tasks
|
|
77
97
|
env:
|
|
78
98
|
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
79
|
-
run:
|
|
99
|
+
run: curl -fsS -X POST http://127.0.0.1:4310/tasks/close
|
|
100
|
+
|
|
101
|
+
- name: Stop orchestration server
|
|
102
|
+
if: always()
|
|
103
|
+
run: |
|
|
104
|
+
if [ -f /tmp/agentic-orchestration.pid ]; then
|
|
105
|
+
kill $(cat /tmp/agentic-orchestration.pid) || true
|
|
106
|
+
fi
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function runtimeLibScript() {
|
|
111
|
+
return `#!/usr/bin/env node
|
|
112
|
+
import fs from "node:fs";
|
|
113
|
+
import path from "node:path";
|
|
114
|
+
import { execFileSync } from "node:child_process";
|
|
115
|
+
|
|
116
|
+
export function ensureDir(dir) {
|
|
117
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function readJson(filePath, fallback = null) {
|
|
121
|
+
if (!fs.existsSync(filePath)) return fallback;
|
|
122
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function writeJson(filePath, payload) {
|
|
126
|
+
ensureDir(path.dirname(filePath));
|
|
127
|
+
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\\n");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function generatedDir() {
|
|
131
|
+
return path.resolve(".agentic-dev/generated");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function generatedPath(name) {
|
|
135
|
+
return path.join(generatedDir(), name);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function orchestrationConfig() {
|
|
139
|
+
return readJson(path.resolve(".agentic-dev/orchestration.json"), {});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function ghJson(args) {
|
|
143
|
+
return JSON.parse(execFileSync("gh", args, { encoding: "utf-8" }));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function gh(args) {
|
|
147
|
+
return execFileSync("gh", args, { encoding: "utf-8" }).trim();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function loadTaskIr() {
|
|
151
|
+
return readJson(generatedPath("task-ir.json"), { tasks: [] });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function loadQueue() {
|
|
155
|
+
return readJson(generatedPath("agent-queue.json"), { queue: [] });
|
|
156
|
+
}
|
|
80
157
|
`;
|
|
81
158
|
}
|
|
82
159
|
|
|
@@ -84,6 +161,7 @@ function sddToIrScript() {
|
|
|
84
161
|
return `#!/usr/bin/env node
|
|
85
162
|
import fs from "node:fs";
|
|
86
163
|
import path from "node:path";
|
|
164
|
+
import { generatedPath, writeJson } from "./runtime-lib.mjs";
|
|
87
165
|
|
|
88
166
|
function walk(root) {
|
|
89
167
|
if (!fs.existsSync(root)) return [];
|
|
@@ -113,10 +191,8 @@ function parseChecklist(markdown, source) {
|
|
|
113
191
|
const planRoot = path.resolve("sdd/02_plan");
|
|
114
192
|
const files = walk(planRoot);
|
|
115
193
|
const tasks = files.flatMap((file) => parseChecklist(fs.readFileSync(file, "utf-8"), path.relative(process.cwd(), file)));
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
const outputPath = path.join(outputDir, "task-ir.json");
|
|
119
|
-
fs.writeFileSync(outputPath, JSON.stringify({ generated_at: new Date().toISOString(), tasks }, null, 2) + "\\n");
|
|
194
|
+
const outputPath = generatedPath("task-ir.json");
|
|
195
|
+
writeJson(outputPath, { generated_at: new Date().toISOString(), tasks });
|
|
120
196
|
console.log(\`task_ir=\${outputPath}\`);
|
|
121
197
|
console.log(\`tasks=\${tasks.length}\`);
|
|
122
198
|
`;
|
|
@@ -124,43 +200,37 @@ console.log(\`tasks=\${tasks.length}\`);
|
|
|
124
200
|
|
|
125
201
|
function syncProjectTasksScript() {
|
|
126
202
|
return `#!/usr/bin/env node
|
|
127
|
-
import
|
|
128
|
-
import { execFileSync } from "node:child_process";
|
|
129
|
-
|
|
130
|
-
const orchestration = JSON.parse(fs.readFileSync(".agentic-dev/orchestration.json", "utf-8"));
|
|
131
|
-
const taskIr = JSON.parse(fs.readFileSync(".agentic-dev/generated/task-ir.json", "utf-8"));
|
|
132
|
-
|
|
133
|
-
function ghJson(args) {
|
|
134
|
-
return JSON.parse(execFileSync("gh", args, { encoding: "utf-8" }));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function gh(args) {
|
|
138
|
-
return execFileSync("gh", args, { encoding: "utf-8" }).trim();
|
|
139
|
-
}
|
|
203
|
+
import { gh, ghJson, loadTaskIr, orchestrationConfig, writeJson, generatedPath } from "./runtime-lib.mjs";
|
|
140
204
|
|
|
205
|
+
const orchestration = orchestrationConfig();
|
|
206
|
+
const taskIr = loadTaskIr();
|
|
141
207
|
const existingIssues = ghJson(["issue", "list", "--repo", orchestration.github.repository.slug, "--state", "all", "--limit", "200", "--json", "number,title,state"]);
|
|
208
|
+
const syncResult = { created: [], closed: [] };
|
|
142
209
|
|
|
143
210
|
for (const task of taskIr.tasks) {
|
|
144
211
|
const issueTitle = \`[agentic-task] \${task.id} \${task.title}\`;
|
|
145
212
|
const found = existingIssues.find((issue) => issue.title.startsWith(\`[agentic-task] \${task.id} \`));
|
|
146
213
|
if (!found && task.status === "open") {
|
|
147
|
-
gh(["issue", "create", "--repo", orchestration.github.repository.slug, "--title", issueTitle, "--body", \`SDD source: \${task.source}\\n\\nManaged by agentic-dev orchestration.\`, "--label", "agentic-task"]);
|
|
214
|
+
const url = gh(["issue", "create", "--repo", orchestration.github.repository.slug, "--title", issueTitle, "--body", \`SDD source: \${task.source}\\n\\nManaged by agentic-dev orchestration.\`, "--label", "agentic-task"]);
|
|
215
|
+
syncResult.created.push({ id: task.id, title: task.title, issue_url: url });
|
|
148
216
|
}
|
|
149
217
|
if (found && task.status === "closed" && found.state !== "CLOSED") {
|
|
150
218
|
gh(["issue", "close", String(found.number), "--repo", orchestration.github.repository.slug, "--comment", "Closed from SDD task IR."]);
|
|
219
|
+
syncResult.closed.push({ id: task.id, issue_number: found.number });
|
|
151
220
|
}
|
|
152
221
|
}
|
|
153
222
|
|
|
223
|
+
writeJson(generatedPath("task-sync.json"), syncResult);
|
|
154
224
|
console.log(\`synced_tasks=\${taskIr.tasks.length}\`);
|
|
155
225
|
`;
|
|
156
226
|
}
|
|
157
227
|
|
|
158
228
|
function runQueueScript() {
|
|
159
229
|
return `#!/usr/bin/env node
|
|
160
|
-
import
|
|
230
|
+
import { generatedPath, loadTaskIr, orchestrationConfig, writeJson } from "./runtime-lib.mjs";
|
|
161
231
|
|
|
162
|
-
const orchestration =
|
|
163
|
-
const taskIr =
|
|
232
|
+
const orchestration = orchestrationConfig();
|
|
233
|
+
const taskIr = loadTaskIr();
|
|
164
234
|
|
|
165
235
|
function classifyAgent(title) {
|
|
166
236
|
const lower = title.toLowerCase();
|
|
@@ -176,37 +246,142 @@ const openTasks = taskIr.tasks.filter((task) => task.status === "open");
|
|
|
176
246
|
const queue = openTasks.map((task) => ({
|
|
177
247
|
...task,
|
|
178
248
|
assigned_agent: classifyAgent(task.title),
|
|
249
|
+
preferred_provider:
|
|
250
|
+
orchestration.providers.find((provider) => provider.startsWith("codex")) ||
|
|
251
|
+
orchestration.providers[0] ||
|
|
252
|
+
"codex-subscription",
|
|
179
253
|
}));
|
|
180
254
|
|
|
181
|
-
const outputPath = "
|
|
182
|
-
|
|
255
|
+
const outputPath = generatedPath("agent-queue.json");
|
|
256
|
+
writeJson(outputPath, { providers: orchestration.providers, queue });
|
|
183
257
|
console.log(\`agent_queue=\${outputPath}\`);
|
|
184
258
|
console.log(\`queued=\${queue.length}\`);
|
|
185
259
|
`;
|
|
186
260
|
}
|
|
187
261
|
|
|
188
|
-
function
|
|
262
|
+
function dispatchQueueScript() {
|
|
189
263
|
return `#!/usr/bin/env node
|
|
190
|
-
import
|
|
191
|
-
|
|
264
|
+
import { generatedPath, loadQueue, orchestrationConfig, writeJson } from "./runtime-lib.mjs";
|
|
265
|
+
|
|
266
|
+
const orchestration = orchestrationConfig();
|
|
267
|
+
const queuePayload = loadQueue();
|
|
268
|
+
|
|
269
|
+
const dispatch = queuePayload.queue.map((task) => ({
|
|
270
|
+
task_id: task.id,
|
|
271
|
+
title: task.title,
|
|
272
|
+
assigned_agent: task.assigned_agent,
|
|
273
|
+
provider: task.preferred_provider,
|
|
274
|
+
repository: orchestration.github.repository.slug,
|
|
275
|
+
project_title: orchestration.github.project.title,
|
|
276
|
+
execution_state: "planned",
|
|
277
|
+
}));
|
|
192
278
|
|
|
193
|
-
const
|
|
194
|
-
|
|
279
|
+
const outputPath = generatedPath("dispatch-plan.json");
|
|
280
|
+
writeJson(outputPath, {
|
|
281
|
+
generated_at: new Date().toISOString(),
|
|
282
|
+
dispatch,
|
|
283
|
+
});
|
|
284
|
+
console.log(\`dispatch_plan=\${outputPath}\`);
|
|
285
|
+
console.log(\`planned=\${dispatch.length}\`);
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function closeTasksScript() {
|
|
290
|
+
return `#!/usr/bin/env node
|
|
291
|
+
import { gh, ghJson, generatedPath, loadTaskIr, orchestrationConfig, writeJson } from "./runtime-lib.mjs";
|
|
195
292
|
|
|
293
|
+
const orchestration = orchestrationConfig();
|
|
294
|
+
const taskIr = loadTaskIr();
|
|
196
295
|
const closedIds = new Set(taskIr.tasks.filter((task) => task.status === "closed").map((task) => task.id));
|
|
197
|
-
const issues =
|
|
296
|
+
const issues = ghJson(["issue", "list", "--repo", orchestration.github.repository.slug, "--state", "open", "--limit", "200", "--json", "number,title"]);
|
|
297
|
+
const closed = [];
|
|
198
298
|
|
|
199
299
|
for (const issue of issues) {
|
|
200
300
|
const match = issue.title.match(/^\\[agentic-task\\] ([^ ]+) /);
|
|
201
301
|
if (!match) continue;
|
|
202
302
|
if (!closedIds.has(match[1])) continue;
|
|
203
|
-
|
|
303
|
+
gh(["issue", "close", String(issue.number), "--repo", orchestration.github.repository.slug, "--comment", "Closed from SDD orchestration close pass."]);
|
|
304
|
+
closed.push({ id: match[1], issue_number: issue.number });
|
|
204
305
|
}
|
|
205
306
|
|
|
307
|
+
writeJson(generatedPath("closed-tasks.json"), { closed });
|
|
206
308
|
console.log(\`closed_candidates=\${closedIds.size}\`);
|
|
207
309
|
`;
|
|
208
310
|
}
|
|
209
311
|
|
|
312
|
+
function serverScript() {
|
|
313
|
+
return `#!/usr/bin/env node
|
|
314
|
+
import http from "node:http";
|
|
315
|
+
import { spawn } from "node:child_process";
|
|
316
|
+
|
|
317
|
+
const port = Number(process.env.AGENTIC_ORCHESTRATION_PORT || 4310);
|
|
318
|
+
|
|
319
|
+
function runNodeScript(script) {
|
|
320
|
+
return new Promise((resolve, reject) => {
|
|
321
|
+
const child = spawn(process.execPath, [script], {
|
|
322
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
let stdout = "";
|
|
326
|
+
let stderr = "";
|
|
327
|
+
|
|
328
|
+
child.stdout.on("data", (chunk) => {
|
|
329
|
+
stdout += chunk.toString();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
child.stderr.on("data", (chunk) => {
|
|
333
|
+
stderr += chunk.toString();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
child.on("close", (code) => {
|
|
337
|
+
if (code !== 0) {
|
|
338
|
+
reject(new Error(stderr || stdout || \`Script failed: \${script}\`));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
resolve({ stdout, stderr });
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const routes = {
|
|
347
|
+
"POST /sync/ir": ".agentic-dev/runtime/sdd_to_ir.mjs",
|
|
348
|
+
"POST /sync/tasks": ".agentic-dev/runtime/sync_project_tasks.mjs",
|
|
349
|
+
"POST /queue/plan": ".agentic-dev/runtime/run_multi_agent_queue.mjs",
|
|
350
|
+
"POST /queue/dispatch": ".agentic-dev/runtime/dispatch_agents.mjs",
|
|
351
|
+
"POST /tasks/close": ".agentic-dev/runtime/close_completed_tasks.mjs",
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const server = http.createServer(async (req, res) => {
|
|
355
|
+
const routeKey = \`\${req.method} \${req.url}\`;
|
|
356
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
357
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
358
|
+
res.end(JSON.stringify({ ok: true, port }));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const script = routes[routeKey];
|
|
363
|
+
if (!script) {
|
|
364
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
365
|
+
res.end(JSON.stringify({ error: "not_found" }));
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const result = await runNodeScript(script);
|
|
371
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
372
|
+
res.end(JSON.stringify({ ok: true, stdout: result.stdout.trim() }));
|
|
373
|
+
} catch (error) {
|
|
374
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
375
|
+
res.end(JSON.stringify({ ok: false, error: String(error.message || error) }));
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
server.listen(port, "127.0.0.1", () => {
|
|
380
|
+
console.log(\`agentic_orchestration_server=http://127.0.0.1:\${port}\`);
|
|
381
|
+
});
|
|
382
|
+
`;
|
|
383
|
+
}
|
|
384
|
+
|
|
210
385
|
export function installSharedAgentAssets(destinationRoot) {
|
|
211
386
|
const root = repoRootDir();
|
|
212
387
|
copyRecursive(path.join(root, ".agent"), path.join(destinationRoot, ".agent"));
|
|
@@ -216,10 +391,13 @@ export function installSharedAgentAssets(destinationRoot) {
|
|
|
216
391
|
|
|
217
392
|
export function installOrchestrationAssets(destinationRoot) {
|
|
218
393
|
writeFile(path.join(destinationRoot, ".github/workflows/agentic-orchestration.yml"), workflowYaml());
|
|
394
|
+
writeFile(path.join(destinationRoot, ".agentic-dev/runtime/runtime-lib.mjs"), runtimeLibScript());
|
|
219
395
|
writeFile(path.join(destinationRoot, ".agentic-dev/runtime/sdd_to_ir.mjs"), sddToIrScript());
|
|
220
396
|
writeFile(path.join(destinationRoot, ".agentic-dev/runtime/sync_project_tasks.mjs"), syncProjectTasksScript());
|
|
221
397
|
writeFile(path.join(destinationRoot, ".agentic-dev/runtime/run_multi_agent_queue.mjs"), runQueueScript());
|
|
398
|
+
writeFile(path.join(destinationRoot, ".agentic-dev/runtime/dispatch_agents.mjs"), dispatchQueueScript());
|
|
222
399
|
writeFile(path.join(destinationRoot, ".agentic-dev/runtime/close_completed_tasks.mjs"), closeTasksScript());
|
|
400
|
+
writeFile(path.join(destinationRoot, ".agentic-dev/runtime/server.mjs"), serverScript());
|
|
223
401
|
}
|
|
224
402
|
|
|
225
403
|
export function buildOrchestrationConfig(setupSelections = {}) {
|
|
@@ -235,7 +413,10 @@ export function buildOrchestrationConfig(setupSelections = {}) {
|
|
|
235
413
|
workflow: {
|
|
236
414
|
ir_output: ".agentic-dev/generated/task-ir.json",
|
|
237
415
|
queue_output: ".agentic-dev/generated/agent-queue.json",
|
|
416
|
+
dispatch_output: ".agentic-dev/generated/dispatch-plan.json",
|
|
238
417
|
workflow_file: ".github/workflows/agentic-orchestration.yml",
|
|
418
|
+
server_entry: ".agentic-dev/runtime/server.mjs",
|
|
419
|
+
server_port: 4310,
|
|
239
420
|
},
|
|
240
421
|
};
|
|
241
422
|
}
|
package/package.json
CHANGED