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 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. `.env.example -> .env`
59
- 9. `pnpm install`
60
- 10. `app mode != backend`이면 Playwright/bootstrap
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 만들고, GitHub task mirror multi-agent queue를 갱신하는 구조다.
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: node .agentic-dev/runtime/sdd_to_ir.mjs
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: node .agentic-dev/runtime/sync_project_tasks.mjs
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: node .agentic-dev/runtime/run_multi_agent_queue.mjs
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: node .agentic-dev/runtime/close_completed_tasks.mjs
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 outputDir = path.resolve(".agentic-dev/generated");
117
- fs.mkdirSync(outputDir, { recursive: true });
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 fs from "node:fs";
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 fs from "node:fs";
230
+ import { generatedPath, loadTaskIr, orchestrationConfig, writeJson } from "./runtime-lib.mjs";
161
231
 
162
- const orchestration = JSON.parse(fs.readFileSync(".agentic-dev/orchestration.json", "utf-8"));
163
- const taskIr = JSON.parse(fs.readFileSync(".agentic-dev/generated/task-ir.json", "utf-8"));
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 = ".agentic-dev/generated/agent-queue.json";
182
- fs.writeFileSync(outputPath, JSON.stringify({ providers: orchestration.providers, queue }, null, 2) + "\\n");
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 closeTasksScript() {
262
+ function dispatchQueueScript() {
189
263
  return `#!/usr/bin/env node
190
- import fs from "node:fs";
191
- import { execFileSync } from "node:child_process";
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 orchestration = JSON.parse(fs.readFileSync(".agentic-dev/orchestration.json", "utf-8"));
194
- const taskIr = JSON.parse(fs.readFileSync(".agentic-dev/generated/task-ir.json", "utf-8"));
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 = JSON.parse(execFileSync("gh", ["issue", "list", "--repo", orchestration.github.repository.slug, "--state", "open", "--limit", "200", "--json", "number,title"], { encoding: "utf-8" }));
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
- execFileSync("gh", ["issue", "close", String(issue.number), "--repo", orchestration.github.repository.slug, "--comment", "Closed from SDD orchestration close pass."], { encoding: "utf-8" });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-dev",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "description": "GitHub Project based installer and multi-agent orchestration CLI for public say828/template-* repos.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.32.0",