agentic-dev 0.2.21 → 0.2.23
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 +17 -9
- package/dist/bin/agentic-dev.js +1 -1
- package/dist/bin/agentic-dev.js.map +1 -1
- package/dist/lib/github.d.ts +4 -0
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +38 -4
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/orchestration-assets.d.ts.map +1 -1
- package/dist/lib/orchestration-assets.js +837 -193
- package/dist/lib/orchestration-assets.js.map +1 -1
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +203 -7
- package/dist/lib/scaffold.js.map +1 -1
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@ on:
|
|
|
21
21
|
push:
|
|
22
22
|
paths:
|
|
23
23
|
- "sdd/02_plan/**"
|
|
24
|
+
- "sdd/99_toolchain/01_automation/github-project-kit/**"
|
|
24
25
|
- ".agentic-dev/orchestration.json"
|
|
25
26
|
- ".agentic-dev/runtime/**"
|
|
26
27
|
workflow_dispatch:
|
|
@@ -31,6 +32,7 @@ jobs:
|
|
|
31
32
|
permissions:
|
|
32
33
|
contents: read
|
|
33
34
|
issues: write
|
|
35
|
+
pull-requests: write
|
|
34
36
|
repository-projects: write
|
|
35
37
|
steps:
|
|
36
38
|
- name: Checkout
|
|
@@ -41,9 +43,25 @@ jobs:
|
|
|
41
43
|
with:
|
|
42
44
|
node-version: "22"
|
|
43
45
|
|
|
44
|
-
- name:
|
|
46
|
+
- name: Resolve orchestration GitHub auth
|
|
45
47
|
env:
|
|
46
|
-
|
|
48
|
+
AGENTIC_GITHUB_TOKEN_SECRET: \${{ secrets.AGENTIC_GITHUB_TOKEN }}
|
|
49
|
+
FALLBACK_GITHUB_TOKEN: \${{ github.token }}
|
|
50
|
+
run: |
|
|
51
|
+
TOKEN="$AGENTIC_GITHUB_TOKEN_SECRET"
|
|
52
|
+
if [ -z "$TOKEN" ]; then
|
|
53
|
+
TOKEN="$FALLBACK_GITHUB_TOKEN"
|
|
54
|
+
fi
|
|
55
|
+
if [ -z "$TOKEN" ]; then
|
|
56
|
+
echo "No GitHub token available for orchestration" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
echo "::add-mask::$TOKEN"
|
|
60
|
+
echo "AGENTIC_GITHUB_TOKEN=$TOKEN" >> "$GITHUB_ENV"
|
|
61
|
+
echo "GH_TOKEN=$TOKEN" >> "$GITHUB_ENV"
|
|
62
|
+
echo "GITHUB_TOKEN=$TOKEN" >> "$GITHUB_ENV"
|
|
63
|
+
|
|
64
|
+
- name: Start orchestration server
|
|
47
65
|
run: |
|
|
48
66
|
npx --yes tsx .agentic-dev/runtime/server.ts > /tmp/agentic-orchestration.log 2>&1 &
|
|
49
67
|
echo $! > /tmp/agentic-orchestration.pid
|
|
@@ -60,29 +78,20 @@ jobs:
|
|
|
60
78
|
run: curl -fsS -X POST http://127.0.0.1:4310/sync/ir
|
|
61
79
|
|
|
62
80
|
- name: Sync GitHub project tasks
|
|
63
|
-
env:
|
|
64
|
-
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
65
81
|
run: curl -fsS -X POST http://127.0.0.1:4310/sync/tasks
|
|
66
82
|
|
|
67
83
|
- name: Plan multi-agent queue
|
|
68
|
-
env:
|
|
69
|
-
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
70
84
|
run: curl -fsS -X POST http://127.0.0.1:4310/queue/plan
|
|
71
85
|
|
|
72
86
|
- name: Build dispatch plan
|
|
73
|
-
env:
|
|
74
|
-
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
75
87
|
run: curl -fsS -X POST http://127.0.0.1:4310/queue/dispatch
|
|
76
88
|
|
|
77
89
|
- name: Execute dispatch queue
|
|
78
90
|
env:
|
|
79
|
-
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
80
91
|
AGENTIC_AGENT_TIMEOUT_MS: "30000"
|
|
81
92
|
run: curl -fsS -X POST http://127.0.0.1:4310/queue/execute
|
|
82
93
|
|
|
83
94
|
- name: Close completed tasks
|
|
84
|
-
env:
|
|
85
|
-
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
86
95
|
run: curl -fsS -X POST http://127.0.0.1:4310/tasks/close
|
|
87
96
|
|
|
88
97
|
- name: Stop orchestration server
|
|
@@ -125,12 +134,97 @@ export function orchestrationConfig() {
|
|
|
125
134
|
return readJson(path.resolve(".agentic-dev/orchestration.json"), {});
|
|
126
135
|
}
|
|
127
136
|
|
|
137
|
+
export function githubAuthEnv() {
|
|
138
|
+
const env = { ...process.env };
|
|
139
|
+
const token = String(env.AGENTIC_GITHUB_TOKEN || env.GH_TOKEN || env.GITHUB_TOKEN || "").trim();
|
|
140
|
+
if (token) {
|
|
141
|
+
env.AGENTIC_GITHUB_TOKEN = env.AGENTIC_GITHUB_TOKEN || token;
|
|
142
|
+
env.GH_TOKEN = token;
|
|
143
|
+
env.GITHUB_TOKEN = token;
|
|
144
|
+
}
|
|
145
|
+
return env;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function projectContractPath() {
|
|
149
|
+
return path.resolve("sdd/99_toolchain/01_automation/github-project-kit/project-contract.json");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function roleMapPath() {
|
|
153
|
+
return path.resolve("sdd/99_toolchain/01_automation/github-project-kit/role-map.json");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function loadProjectContract() {
|
|
157
|
+
return readJson(projectContractPath(), null);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function loadRoleMap() {
|
|
161
|
+
return readJson(roleMapPath(), { roles: [] });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function artifactContractPath() {
|
|
165
|
+
return path.resolve("sdd/99_toolchain/01_automation/github-project-kit/artifact-contract.json");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function loadArtifactContract() {
|
|
169
|
+
return readJson(artifactContractPath(), null);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function taskCatalogPath() {
|
|
173
|
+
const contract = loadArtifactContract();
|
|
174
|
+
return path.resolve(
|
|
175
|
+
String(contract?.outputs?.task_catalog || "sdd/99_toolchain/01_automation/github-project-kit/task-catalog.json"),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function taskSyncStatePath() {
|
|
180
|
+
const contract = loadArtifactContract();
|
|
181
|
+
return path.resolve(
|
|
182
|
+
String(contract?.outputs?.task_sync_state || "sdd/99_toolchain/01_automation/github-project-kit/task-sync-state.json"),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function loadTaskCatalog() {
|
|
187
|
+
return readJson(taskCatalogPath(), { tasks: [] });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function loadTaskSyncState() {
|
|
191
|
+
return readJson(taskSyncStatePath(), { issues: {} });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function hasProjectContract() {
|
|
195
|
+
const contract = loadProjectContract();
|
|
196
|
+
return Boolean(contract && contract.project_id && contract.project_number && contract.repository);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function hasArtifactBacklogKit() {
|
|
200
|
+
return Boolean(
|
|
201
|
+
hasProjectContract() &&
|
|
202
|
+
fs.existsSync(artifactContractPath()) &&
|
|
203
|
+
fs.existsSync(path.resolve("scripts/agentic-core/artifact_backlog.py")),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function runArtifactBacklog() {
|
|
208
|
+
const output = execFileSync("python3", ["scripts/agentic-core/artifact_backlog.py", "run", "--repo-root", "."], {
|
|
209
|
+
encoding: "utf-8",
|
|
210
|
+
env: githubAuthEnv(),
|
|
211
|
+
}).trim();
|
|
212
|
+
return output ? JSON.parse(output) : {};
|
|
213
|
+
}
|
|
214
|
+
|
|
128
215
|
export function ghJson(args) {
|
|
129
|
-
|
|
216
|
+
const output = execFileSync("gh", args, {
|
|
217
|
+
encoding: "utf-8",
|
|
218
|
+
env: githubAuthEnv(),
|
|
219
|
+
}).trim();
|
|
220
|
+
return output ? JSON.parse(output) : {};
|
|
130
221
|
}
|
|
131
222
|
|
|
132
223
|
export function gh(args) {
|
|
133
|
-
return execFileSync("gh", args, {
|
|
224
|
+
return execFileSync("gh", args, {
|
|
225
|
+
encoding: "utf-8",
|
|
226
|
+
env: githubAuthEnv(),
|
|
227
|
+
}).trim();
|
|
134
228
|
}
|
|
135
229
|
|
|
136
230
|
export function loadTaskIr() {
|
|
@@ -153,6 +247,30 @@ export function loadExecutionJournal() {
|
|
|
153
247
|
return readJson(generatedPath("execution-journal.json"), { executions: [] });
|
|
154
248
|
}
|
|
155
249
|
|
|
250
|
+
export function issueUrl(repository, issueNumber) {
|
|
251
|
+
return "https://github.com/" + String(repository || "") + "/issues/" + String(issueNumber || "");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function issueNumberFromUrl(url) {
|
|
255
|
+
const match = String(url || "").match(/\\/issues\\/(\\d+)$/);
|
|
256
|
+
return match ? Number(match[1]) : null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function normalizeTaskStatus(status) {
|
|
260
|
+
return String(status || "").trim().toLowerCase() === "closed" ? "closed" : "open";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function classifyAgent(title) {
|
|
264
|
+
const lower = String(title || "").toLowerCase();
|
|
265
|
+
if (lower.includes("api") || lower.includes("contract")) return "api";
|
|
266
|
+
if (lower.includes("screen") || lower.includes("ui")) return "ui";
|
|
267
|
+
if (lower.includes("verify") || lower.includes("test") || lower.includes("proof")) return "quality";
|
|
268
|
+
if (lower.includes("workflow") || lower.includes("project") || lower.includes("github")) return "gitops";
|
|
269
|
+
if (lower.includes("arch") || lower.includes("boundary") || lower.includes("structure")) return "architecture";
|
|
270
|
+
if (lower.includes("plan") || lower.includes("spec")) return "specs";
|
|
271
|
+
return "runtime";
|
|
272
|
+
}
|
|
273
|
+
|
|
156
274
|
export function normalizeProviderProfile(profile) {
|
|
157
275
|
const normalized = String(profile || "").trim().toLowerCase();
|
|
158
276
|
switch (normalized) {
|
|
@@ -174,6 +292,217 @@ export function normalizeProviderProfile(profile) {
|
|
|
174
292
|
return normalized;
|
|
175
293
|
}
|
|
176
294
|
}
|
|
295
|
+
|
|
296
|
+
export function pickProvider(title, providers) {
|
|
297
|
+
const normalizedProviders = (Array.isArray(providers) ? providers : []).map(normalizeProviderProfile);
|
|
298
|
+
const lower = String(title || "").toLowerCase();
|
|
299
|
+
if (lower.includes("verify") || lower.includes("test")) {
|
|
300
|
+
return (
|
|
301
|
+
normalizedProviders.find((provider) => provider === "claude-cli" || provider === "anthropic-api") ||
|
|
302
|
+
normalizedProviders[0] ||
|
|
303
|
+
"claude-cli"
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (lower.includes("api") || lower.includes("contract")) {
|
|
307
|
+
return (
|
|
308
|
+
normalizedProviders.find((provider) => provider === "openai-api" || provider === "anthropic-api") ||
|
|
309
|
+
normalizedProviders[0] ||
|
|
310
|
+
"openai-api"
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
return (
|
|
314
|
+
normalizedProviders.find((provider) => provider === "codex-cli" || provider === "openai-api") ||
|
|
315
|
+
normalizedProviders[0] ||
|
|
316
|
+
"codex-cli"
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function roleEntry(roleMap, roleId) {
|
|
321
|
+
const roles = Array.isArray(roleMap?.roles) ? roleMap.roles : [];
|
|
322
|
+
return roles.find((entry) => String(entry?.id || "") === String(roleId || "")) || null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function resolveAgentRole(roleMap, preferredRole) {
|
|
326
|
+
const normalized = String(preferredRole || "").trim() || "runtime";
|
|
327
|
+
if (roleEntry(roleMap, normalized)) return normalized;
|
|
328
|
+
if (roleEntry(roleMap, "runtime")) return "runtime";
|
|
329
|
+
return normalized;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function taskIssueMarker(taskId) {
|
|
333
|
+
return "<!-- agentic-task-key: " + String(taskId || "") + " -->";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function taskIssueTitle(task) {
|
|
337
|
+
return "[agentic-task] " + String(task?.id || "") + " " + String(task?.title || "");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function taskIssueBody(task, assignedAgent) {
|
|
341
|
+
return [
|
|
342
|
+
taskIssueMarker(task.id),
|
|
343
|
+
"<!-- agentic-task-meta " +
|
|
344
|
+
JSON.stringify({
|
|
345
|
+
id: String(task?.id || ""),
|
|
346
|
+
title: String(task?.title || ""),
|
|
347
|
+
source: String(task?.source || ""),
|
|
348
|
+
status: normalizeTaskStatus(task?.status),
|
|
349
|
+
agent_role: String(assignedAgent || "") || null,
|
|
350
|
+
}) +
|
|
351
|
+
" -->",
|
|
352
|
+
"agent_role: " + String(assignedAgent || ""),
|
|
353
|
+
"SDD source: " + String(task?.source || ""),
|
|
354
|
+
"",
|
|
355
|
+
"Managed by agentic-dev orchestration.",
|
|
356
|
+
].join("\\n");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function findIssueForTask(issues, taskId) {
|
|
360
|
+
return (
|
|
361
|
+
(Array.isArray(issues) ? issues : []).find((issue) => {
|
|
362
|
+
const body = String(issue?.body || "");
|
|
363
|
+
const title = String(issue?.title || "");
|
|
364
|
+
return body.includes(taskIssueMarker(taskId)) || title.startsWith("[agentic-task] " + taskId + " ");
|
|
365
|
+
}) || null
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function projectContractAvailable(contract) {
|
|
370
|
+
return Boolean(
|
|
371
|
+
contract &&
|
|
372
|
+
contract.project_id &&
|
|
373
|
+
contract.project_number &&
|
|
374
|
+
contract.owner &&
|
|
375
|
+
contract.repository,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function projectItemIndex(contract) {
|
|
380
|
+
if (!projectContractAvailable(contract)) {
|
|
381
|
+
return new Map();
|
|
382
|
+
}
|
|
383
|
+
const payload = ghJson([
|
|
384
|
+
"project",
|
|
385
|
+
"item-list",
|
|
386
|
+
String(contract.project_number),
|
|
387
|
+
"--owner",
|
|
388
|
+
String(contract.owner),
|
|
389
|
+
"--limit",
|
|
390
|
+
"200",
|
|
391
|
+
"--format",
|
|
392
|
+
"json",
|
|
393
|
+
]);
|
|
394
|
+
const index = new Map();
|
|
395
|
+
for (const item of Array.isArray(payload?.items) ? payload.items : []) {
|
|
396
|
+
const content = item?.content || {};
|
|
397
|
+
if (content.type === "Issue" && typeof content.number === "number") {
|
|
398
|
+
index.set(content.number, item);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return index;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function ensureProjectItem(contract, issueNumber, itemIndex) {
|
|
405
|
+
if (!projectContractAvailable(contract)) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const existing = itemIndex?.get(issueNumber);
|
|
409
|
+
if (existing) {
|
|
410
|
+
return existing;
|
|
411
|
+
}
|
|
412
|
+
const payload = ghJson([
|
|
413
|
+
"project",
|
|
414
|
+
"item-add",
|
|
415
|
+
String(contract.project_number),
|
|
416
|
+
"--owner",
|
|
417
|
+
String(contract.owner),
|
|
418
|
+
"--url",
|
|
419
|
+
issueUrl(contract.repository, issueNumber),
|
|
420
|
+
"--format",
|
|
421
|
+
"json",
|
|
422
|
+
]);
|
|
423
|
+
if (!payload?.id) {
|
|
424
|
+
throw new Error("unable to add project item for issue #" + String(issueNumber));
|
|
425
|
+
}
|
|
426
|
+
const added = {
|
|
427
|
+
id: String(payload.id),
|
|
428
|
+
content: {
|
|
429
|
+
type: String(payload.type || "Issue"),
|
|
430
|
+
number: Number(issueNumber),
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
if (itemIndex) {
|
|
434
|
+
itemIndex.set(Number(issueNumber), added);
|
|
435
|
+
}
|
|
436
|
+
return added;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function setProjectStatus(contract, itemId, statusKey) {
|
|
440
|
+
const fieldId = String(contract?.fields?.status?.id || "");
|
|
441
|
+
const optionId = String(contract?.fields?.status?.options?.[statusKey] || "");
|
|
442
|
+
const projectId = String(contract?.project_id || "");
|
|
443
|
+
if (!itemId || !fieldId || !optionId || !projectId) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
gh([
|
|
447
|
+
"project",
|
|
448
|
+
"item-edit",
|
|
449
|
+
"--id",
|
|
450
|
+
String(itemId),
|
|
451
|
+
"--project-id",
|
|
452
|
+
projectId,
|
|
453
|
+
"--field-id",
|
|
454
|
+
fieldId,
|
|
455
|
+
"--single-select-option-id",
|
|
456
|
+
optionId,
|
|
457
|
+
]);
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function setProjectText(contract, itemId, fieldId, text) {
|
|
462
|
+
const projectId = String(contract?.project_id || "");
|
|
463
|
+
if (!itemId || !fieldId || !projectId) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
gh([
|
|
467
|
+
"project",
|
|
468
|
+
"item-edit",
|
|
469
|
+
"--id",
|
|
470
|
+
String(itemId),
|
|
471
|
+
"--project-id",
|
|
472
|
+
projectId,
|
|
473
|
+
"--field-id",
|
|
474
|
+
String(fieldId),
|
|
475
|
+
"--text",
|
|
476
|
+
String(text || ""),
|
|
477
|
+
]);
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function setProjectAgentRole(contract, itemId, agentRole) {
|
|
482
|
+
return setProjectText(contract, itemId, String(contract?.fields?.agent_role?.id || ""), String(agentRole || ""));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function buildProjectLogComment(contract, payload = {}) {
|
|
486
|
+
const lines = [String(contract?.structured_comment_prefix || "<!-- agentic-project-log -->")];
|
|
487
|
+
if (payload.agent_role) lines.push("agent_role: " + String(payload.agent_role));
|
|
488
|
+
if (payload.status) lines.push("status: " + String(payload.status));
|
|
489
|
+
if (payload.branch) lines.push("branch: " + String(payload.branch));
|
|
490
|
+
if (payload.summary) lines.push("summary: " + String(payload.summary));
|
|
491
|
+
if (payload.next) lines.push("next: " + String(payload.next));
|
|
492
|
+
return lines.join("\\n");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function parseTaskMeta(body) {
|
|
496
|
+
const match = String(body || "").match(/<!-- agentic-task-meta\\s*([\\s\\S]*?)\\s*-->/);
|
|
497
|
+
if (!match) {
|
|
498
|
+
return {};
|
|
499
|
+
}
|
|
500
|
+
try {
|
|
501
|
+
return JSON.parse(match[1]);
|
|
502
|
+
} catch {
|
|
503
|
+
return {};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
177
506
|
`;
|
|
178
507
|
}
|
|
179
508
|
function sddToIrScript() {
|
|
@@ -220,120 +549,323 @@ console.log(\`tasks=\${tasks.length}\`);
|
|
|
220
549
|
}
|
|
221
550
|
function syncProjectTasksScript() {
|
|
222
551
|
return `#!/usr/bin/env node
|
|
223
|
-
import {
|
|
552
|
+
import {
|
|
553
|
+
artifactContractPath,
|
|
554
|
+
ensureProjectItem,
|
|
555
|
+
findIssueForTask,
|
|
556
|
+
generatedPath,
|
|
557
|
+
gh,
|
|
558
|
+
ghJson,
|
|
559
|
+
hasArtifactBacklogKit,
|
|
560
|
+
issueNumberFromUrl,
|
|
561
|
+
issueUrl,
|
|
562
|
+
loadArtifactContract,
|
|
563
|
+
loadProjectContract,
|
|
564
|
+
loadRoleMap,
|
|
565
|
+
loadTaskCatalog,
|
|
566
|
+
loadTaskSyncState,
|
|
567
|
+
loadTaskIr,
|
|
568
|
+
normalizeTaskStatus,
|
|
569
|
+
orchestrationConfig,
|
|
570
|
+
parseTaskMeta,
|
|
571
|
+
projectContractAvailable,
|
|
572
|
+
projectContractPath,
|
|
573
|
+
projectItemIndex,
|
|
574
|
+
resolveAgentRole,
|
|
575
|
+
roleMapPath,
|
|
576
|
+
runArtifactBacklog,
|
|
577
|
+
setProjectAgentRole,
|
|
578
|
+
setProjectStatus,
|
|
579
|
+
taskIssueBody,
|
|
580
|
+
taskIssueTitle,
|
|
581
|
+
classifyAgent,
|
|
582
|
+
writeJson,
|
|
583
|
+
} from "./runtime-lib.ts";
|
|
224
584
|
|
|
225
585
|
const orchestration = orchestrationConfig();
|
|
586
|
+
const repository = orchestration?.github?.repository?.slug;
|
|
587
|
+
if (!repository) {
|
|
588
|
+
throw new Error("github repository slug is not configured in .agentic-dev/orchestration.json");
|
|
589
|
+
}
|
|
590
|
+
|
|
226
591
|
const taskIr = loadTaskIr();
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
592
|
+
const artifactContract = loadArtifactContract();
|
|
593
|
+
const projectContract = loadProjectContract();
|
|
594
|
+
const roleMap = loadRoleMap();
|
|
595
|
+
function loadIssues() {
|
|
596
|
+
const payload = ghJson([
|
|
597
|
+
"issue",
|
|
598
|
+
"list",
|
|
599
|
+
"--repo",
|
|
600
|
+
repository,
|
|
601
|
+
"--state",
|
|
602
|
+
"all",
|
|
603
|
+
"--limit",
|
|
604
|
+
"200",
|
|
605
|
+
"--json",
|
|
606
|
+
"number,title,state,body,url",
|
|
607
|
+
]);
|
|
608
|
+
return Array.isArray(payload) ? payload : [];
|
|
609
|
+
}
|
|
240
610
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
611
|
+
let existingIssues = loadIssues();
|
|
612
|
+
let itemIndex = projectContractAvailable(projectContract) ? projectItemIndex(projectContract) : new Map();
|
|
613
|
+
const syncResult = {
|
|
614
|
+
mode: "checklist",
|
|
615
|
+
artifact_backlog: null,
|
|
616
|
+
created: [],
|
|
617
|
+
updated: [],
|
|
618
|
+
reopened: [],
|
|
619
|
+
closed: [],
|
|
620
|
+
warnings: [],
|
|
621
|
+
project: {
|
|
622
|
+
enabled: projectContractAvailable(projectContract),
|
|
623
|
+
contract_path: projectContractAvailable(projectContract) ? projectContractPath() : null,
|
|
624
|
+
artifact_contract_path: artifactContract ? artifactContractPath() : null,
|
|
625
|
+
role_map_path: roleMapPath(),
|
|
626
|
+
},
|
|
627
|
+
tasks: [],
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
if (hasArtifactBacklogKit()) {
|
|
631
|
+
syncResult.mode = "artifact-backlog";
|
|
632
|
+
syncResult.artifact_backlog = runArtifactBacklog();
|
|
633
|
+
existingIssues = loadIssues();
|
|
634
|
+
itemIndex = projectContractAvailable(projectContract) ? projectItemIndex(projectContract) : new Map();
|
|
635
|
+
|
|
636
|
+
const catalog = loadTaskCatalog();
|
|
637
|
+
const syncState = loadTaskSyncState();
|
|
638
|
+
const catalogTasks = Array.isArray(catalog?.tasks) ? catalog.tasks : [];
|
|
639
|
+
|
|
640
|
+
for (const task of catalogTasks) {
|
|
641
|
+
const meta = parseTaskMeta(task.body);
|
|
642
|
+
const assignedAgent = resolveAgentRole(
|
|
643
|
+
roleMap,
|
|
644
|
+
String(task.agent_role || meta.agent_role || classifyAgent(task.title)),
|
|
645
|
+
);
|
|
646
|
+
const syncedIssue = syncState?.issues?.[task.key];
|
|
647
|
+
const mappedIssueNumber =
|
|
648
|
+
syncedIssue && typeof syncedIssue.number === "number" ? Number(syncedIssue.number) : null;
|
|
649
|
+
const found =
|
|
650
|
+
(mappedIssueNumber
|
|
651
|
+
? existingIssues.find((issue) => Number(issue?.number) === mappedIssueNumber)
|
|
652
|
+
: null) ||
|
|
653
|
+
findIssueForTask(existingIssues, task.key);
|
|
654
|
+
const taskStatus = normalizeTaskStatus(found?.state === "CLOSED" ? "closed" : "open");
|
|
655
|
+
const issueNumber = found?.number ?? mappedIssueNumber ?? null;
|
|
656
|
+
const issueLink = found?.url || (issueNumber ? issueUrl(repository, issueNumber) : null);
|
|
657
|
+
|
|
658
|
+
let projectItemId = null;
|
|
659
|
+
let projectStatus = null;
|
|
660
|
+
if (issueNumber && projectContractAvailable(projectContract)) {
|
|
661
|
+
try {
|
|
662
|
+
const item = ensureProjectItem(projectContract, Number(issueNumber), itemIndex);
|
|
663
|
+
projectItemId = item?.id || null;
|
|
664
|
+
if (projectItemId) {
|
|
665
|
+
setProjectAgentRole(projectContract, projectItemId, assignedAgent);
|
|
666
|
+
projectStatus = taskStatus === "closed" ? "done" : "todo";
|
|
667
|
+
setProjectStatus(projectContract, projectItemId, projectStatus);
|
|
668
|
+
}
|
|
669
|
+
} catch (error) {
|
|
670
|
+
syncResult.warnings.push({
|
|
671
|
+
id: task.key,
|
|
672
|
+
scope: "artifact-project-sync",
|
|
673
|
+
message: String(error?.message || error),
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
syncResult.tasks.push({
|
|
679
|
+
id: task.key,
|
|
680
|
+
title: task.title,
|
|
681
|
+
source:
|
|
682
|
+
Array.isArray(meta.source_refs) && meta.source_refs.length > 0
|
|
683
|
+
? String(meta.source_refs[0])
|
|
684
|
+
: "artifact:" + String(task.artifact_type || "task"),
|
|
685
|
+
status: taskStatus,
|
|
686
|
+
agent_role: assignedAgent,
|
|
687
|
+
issue_number: issueNumber,
|
|
688
|
+
issue_url: issueLink,
|
|
689
|
+
project_item_id: projectItemId,
|
|
690
|
+
project_status: projectStatus,
|
|
691
|
+
project_enabled: projectContractAvailable(projectContract),
|
|
692
|
+
task_origin: "artifact-backlog",
|
|
693
|
+
artifact_type: task.artifact_type || null,
|
|
694
|
+
source_refs: Array.isArray(meta.source_refs) ? meta.source_refs : [],
|
|
695
|
+
suggested_commands: Array.isArray(meta.suggested_commands) ? meta.suggested_commands : [],
|
|
696
|
+
target_paths_hint: Array.isArray(meta.target_paths_hint) ? meta.target_paths_hint : [],
|
|
697
|
+
priority: task.priority || null,
|
|
698
|
+
size: task.size || null,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (syncResult.tasks.length === 0) {
|
|
704
|
+
syncResult.mode = "checklist";
|
|
705
|
+
|
|
706
|
+
for (const task of taskIr.tasks) {
|
|
707
|
+
const assignedAgent = resolveAgentRole(roleMap, classifyAgent(task.title));
|
|
708
|
+
const issueTitle = taskIssueTitle(task);
|
|
709
|
+
const issueBody = taskIssueBody(task, assignedAgent);
|
|
710
|
+
const taskStatus = normalizeTaskStatus(task.status);
|
|
711
|
+
|
|
712
|
+
let found = findIssueForTask(existingIssues, task.id);
|
|
713
|
+
let issueNumber = found?.number ?? null;
|
|
714
|
+
let issueLink = found?.url || (issueNumber ? issueUrl(repository, issueNumber) : null);
|
|
715
|
+
|
|
716
|
+
if (!found && taskStatus === "open") {
|
|
717
|
+
const createdUrl = gh([
|
|
718
|
+
"issue",
|
|
719
|
+
"create",
|
|
720
|
+
"--repo",
|
|
721
|
+
repository,
|
|
722
|
+
"--title",
|
|
723
|
+
issueTitle,
|
|
724
|
+
"--body",
|
|
725
|
+
issueBody,
|
|
726
|
+
]);
|
|
727
|
+
issueNumber = issueNumberFromUrl(createdUrl);
|
|
728
|
+
issueLink = createdUrl || (issueNumber ? issueUrl(repository, issueNumber) : null);
|
|
729
|
+
found = {
|
|
730
|
+
number: issueNumber,
|
|
731
|
+
title: issueTitle,
|
|
732
|
+
state: "OPEN",
|
|
733
|
+
body: issueBody,
|
|
734
|
+
url: issueLink,
|
|
735
|
+
};
|
|
736
|
+
existingIssues.push(found);
|
|
737
|
+
syncResult.created.push({
|
|
738
|
+
id: task.id,
|
|
739
|
+
title: task.title,
|
|
740
|
+
issue_number: issueNumber,
|
|
741
|
+
issue_url: issueLink,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (found && taskStatus === "open" && found.state === "CLOSED") {
|
|
746
|
+
gh([
|
|
747
|
+
"issue",
|
|
748
|
+
"reopen",
|
|
749
|
+
String(found.number),
|
|
750
|
+
"--repo",
|
|
751
|
+
repository,
|
|
752
|
+
]);
|
|
753
|
+
found.state = "OPEN";
|
|
754
|
+
syncResult.reopened.push({ id: task.id, issue_number: found.number });
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (found && (String(found.title || "") !== issueTitle || String(found.body || "") !== issueBody)) {
|
|
758
|
+
gh([
|
|
759
|
+
"issue",
|
|
760
|
+
"edit",
|
|
761
|
+
String(found.number),
|
|
762
|
+
"--repo",
|
|
763
|
+
repository,
|
|
764
|
+
"--title",
|
|
765
|
+
issueTitle,
|
|
766
|
+
"--body",
|
|
767
|
+
issueBody,
|
|
768
|
+
]);
|
|
769
|
+
found.title = issueTitle;
|
|
770
|
+
found.body = issueBody;
|
|
771
|
+
syncResult.updated.push({ id: task.id, issue_number: found.number });
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (found && taskStatus === "closed" && found.state !== "CLOSED") {
|
|
775
|
+
gh([
|
|
776
|
+
"issue",
|
|
777
|
+
"close",
|
|
778
|
+
String(found.number),
|
|
779
|
+
"--repo",
|
|
780
|
+
repository,
|
|
781
|
+
"--comment",
|
|
782
|
+
"Closed from SDD task IR.",
|
|
783
|
+
]);
|
|
784
|
+
found.state = "CLOSED";
|
|
785
|
+
syncResult.closed.push({ id: task.id, issue_number: found.number });
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
issueNumber = found?.number ?? issueNumber ?? null;
|
|
789
|
+
issueLink = found?.url || issueLink || (issueNumber ? issueUrl(repository, issueNumber) : null);
|
|
790
|
+
|
|
791
|
+
let projectItemId = null;
|
|
792
|
+
let projectStatus = null;
|
|
793
|
+
if (issueNumber && projectContractAvailable(projectContract)) {
|
|
794
|
+
try {
|
|
795
|
+
const item = ensureProjectItem(projectContract, Number(issueNumber), itemIndex);
|
|
796
|
+
projectItemId = item?.id || null;
|
|
797
|
+
if (projectItemId) {
|
|
798
|
+
setProjectAgentRole(projectContract, projectItemId, assignedAgent);
|
|
799
|
+
projectStatus = taskStatus === "closed" ? "done" : "todo";
|
|
800
|
+
setProjectStatus(projectContract, projectItemId, projectStatus);
|
|
801
|
+
}
|
|
802
|
+
} catch (error) {
|
|
803
|
+
syncResult.warnings.push({
|
|
804
|
+
id: task.id,
|
|
805
|
+
scope: "project-sync",
|
|
806
|
+
message: String(error?.message || error),
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
syncResult.tasks.push({
|
|
812
|
+
id: task.id,
|
|
813
|
+
title: task.title,
|
|
814
|
+
source: task.source,
|
|
815
|
+
status: taskStatus,
|
|
816
|
+
agent_role: assignedAgent,
|
|
817
|
+
issue_number: issueNumber,
|
|
818
|
+
issue_url: issueLink,
|
|
819
|
+
project_item_id: projectItemId,
|
|
820
|
+
project_status: projectStatus,
|
|
821
|
+
project_enabled: projectContractAvailable(projectContract),
|
|
822
|
+
task_origin: "checklist",
|
|
823
|
+
});
|
|
258
824
|
}
|
|
259
|
-
if (found && task.status === "closed" && found.state !== "CLOSED") {
|
|
260
|
-
gh([
|
|
261
|
-
"issue",
|
|
262
|
-
"close",
|
|
263
|
-
String(found.number),
|
|
264
|
-
"--repo",
|
|
265
|
-
orchestration.github.repository.slug,
|
|
266
|
-
"--comment",
|
|
267
|
-
"Closed from SDD task IR.",
|
|
268
|
-
]);
|
|
269
|
-
syncResult.closed.push({ id: task.id, issue_number: found.number });
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
syncResult.tasks.push({
|
|
273
|
-
id: task.id,
|
|
274
|
-
title: task.title,
|
|
275
|
-
source: task.source,
|
|
276
|
-
status: task.status,
|
|
277
|
-
issue_number: found?.number ?? null,
|
|
278
|
-
issue_url: found
|
|
279
|
-
? \`https://github.com/\${orchestration.github.repository.slug}/issues/\${found.number}\`
|
|
280
|
-
: null,
|
|
281
|
-
});
|
|
282
825
|
}
|
|
283
826
|
|
|
284
827
|
writeJson(generatedPath("task-sync.json"), syncResult);
|
|
285
|
-
writeJson(generatedPath("task-index.json"), {
|
|
286
|
-
|
|
828
|
+
writeJson(generatedPath("task-index.json"), {
|
|
829
|
+
generated_at: new Date().toISOString(),
|
|
830
|
+
project: syncResult.project,
|
|
831
|
+
tasks: syncResult.tasks,
|
|
832
|
+
});
|
|
833
|
+
console.log(\`sync_mode=\${syncResult.mode}\`);
|
|
834
|
+
console.log(\`synced_tasks=\${syncResult.tasks.length}\`);
|
|
287
835
|
`;
|
|
288
836
|
}
|
|
289
837
|
function runQueueScript() {
|
|
290
838
|
return `#!/usr/bin/env node
|
|
291
|
-
import {
|
|
839
|
+
import {
|
|
840
|
+
classifyAgent,
|
|
841
|
+
generatedPath,
|
|
842
|
+
loadTaskIndex,
|
|
843
|
+
loadTaskIr,
|
|
844
|
+
normalizeTaskStatus,
|
|
845
|
+
orchestrationConfig,
|
|
846
|
+
pickProvider,
|
|
847
|
+
writeJson,
|
|
848
|
+
} from "./runtime-lib.ts";
|
|
292
849
|
|
|
293
850
|
const orchestration = orchestrationConfig();
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const lower = title.toLowerCase();
|
|
298
|
-
if (lower.includes("api") || lower.includes("contract")) return "api";
|
|
299
|
-
if (lower.includes("screen") || lower.includes("ui")) return "ui";
|
|
300
|
-
if (lower.includes("verify") || lower.includes("test") || lower.includes("proof")) return "quality";
|
|
301
|
-
if (lower.includes("workflow") || lower.includes("project") || lower.includes("github")) return "gitops";
|
|
302
|
-
if (lower.includes("arch") || lower.includes("boundary") || lower.includes("structure")) return "architecture";
|
|
303
|
-
if (lower.includes("plan") || lower.includes("spec")) return "specs";
|
|
304
|
-
return "runtime";
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function pickProvider(title) {
|
|
308
|
-
const providers = (Array.isArray(orchestration.providers) ? orchestration.providers : []).map(normalizeProviderProfile);
|
|
309
|
-
const lower = title.toLowerCase();
|
|
310
|
-
if (lower.includes("verify") || lower.includes("test")) {
|
|
311
|
-
return (
|
|
312
|
-
providers.find((provider) => provider === "claude-cli" || provider === "anthropic-api") ||
|
|
313
|
-
providers[0] ||
|
|
314
|
-
"claude-cli"
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
if (lower.includes("api") || lower.includes("contract")) {
|
|
318
|
-
return (
|
|
319
|
-
providers.find((provider) => provider === "openai-api" || provider === "anthropic-api") ||
|
|
320
|
-
providers[0] ||
|
|
321
|
-
"openai-api"
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
return (
|
|
325
|
-
providers.find((provider) => provider === "codex-cli" || provider === "openai-api") ||
|
|
326
|
-
providers[0] ||
|
|
327
|
-
"codex-cli"
|
|
328
|
-
);
|
|
329
|
-
}
|
|
851
|
+
const taskIndex = loadTaskIndex();
|
|
852
|
+
const indexedTasks = Array.isArray(taskIndex.tasks) ? taskIndex.tasks : [];
|
|
853
|
+
const tasks = indexedTasks.length > 0 ? indexedTasks : loadTaskIr().tasks;
|
|
330
854
|
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
855
|
+
const queue = tasks
|
|
856
|
+
.filter((task) => normalizeTaskStatus(task.status) === "open")
|
|
857
|
+
.map((task) => {
|
|
858
|
+
const assignedAgent = String(task.agent_role || classifyAgent(task.title));
|
|
859
|
+
return {
|
|
860
|
+
...task,
|
|
861
|
+
assigned_agent: assignedAgent,
|
|
862
|
+
agent_role: assignedAgent,
|
|
863
|
+
preferred_provider: pickProvider(task.title, orchestration.providers),
|
|
864
|
+
project_item_id: task.project_item_id ?? null,
|
|
865
|
+
project_status: task.project_status ?? null,
|
|
866
|
+
project_enabled: Boolean(task.project_enabled),
|
|
867
|
+
};
|
|
868
|
+
});
|
|
337
869
|
|
|
338
870
|
const outputPath = generatedPath("agent-queue.json");
|
|
339
871
|
writeJson(outputPath, { providers: orchestration.providers, queue });
|
|
@@ -343,19 +875,32 @@ console.log(\`queued=\${queue.length}\`);
|
|
|
343
875
|
}
|
|
344
876
|
function dispatchQueueScript() {
|
|
345
877
|
return `#!/usr/bin/env node
|
|
346
|
-
import { generatedPath, loadQueue, orchestrationConfig, writeJson } from "./runtime-lib.ts";
|
|
878
|
+
import { generatedPath, loadProjectContract, loadQueue, orchestrationConfig, writeJson } from "./runtime-lib.ts";
|
|
347
879
|
|
|
348
880
|
const orchestration = orchestrationConfig();
|
|
881
|
+
const projectContract = loadProjectContract();
|
|
349
882
|
const queuePayload = loadQueue();
|
|
350
883
|
|
|
351
884
|
const dispatch = queuePayload.queue.map((task) => ({
|
|
352
885
|
task_id: task.id,
|
|
353
886
|
title: task.title,
|
|
354
887
|
source: task.source,
|
|
888
|
+
source_refs: Array.isArray(task.source_refs) ? task.source_refs : [],
|
|
889
|
+
suggested_commands: Array.isArray(task.suggested_commands) ? task.suggested_commands : [],
|
|
890
|
+
target_paths_hint: Array.isArray(task.target_paths_hint) ? task.target_paths_hint : [],
|
|
891
|
+
priority: task.priority ?? null,
|
|
892
|
+
size: task.size ?? null,
|
|
893
|
+
task_origin: task.task_origin || "checklist",
|
|
355
894
|
assigned_agent: task.assigned_agent,
|
|
895
|
+
agent_role: task.agent_role || task.assigned_agent,
|
|
356
896
|
provider: task.preferred_provider,
|
|
357
897
|
repository: orchestration.github.repository.slug,
|
|
358
|
-
project_title: orchestration.github.project.title,
|
|
898
|
+
project_title: orchestration.github.project.title || projectContract?.project_title || "",
|
|
899
|
+
issue_number: task.issue_number ?? null,
|
|
900
|
+
issue_url: task.issue_url ?? null,
|
|
901
|
+
project_item_id: task.project_item_id ?? null,
|
|
902
|
+
project_status: task.project_status ?? null,
|
|
903
|
+
project_enabled: Boolean(task.project_enabled),
|
|
359
904
|
execution_state: "planned",
|
|
360
905
|
}));
|
|
361
906
|
|
|
@@ -373,11 +918,24 @@ function dispatchExecutorScript() {
|
|
|
373
918
|
import fs from "node:fs";
|
|
374
919
|
import path from "node:path";
|
|
375
920
|
import { spawnSync } from "node:child_process";
|
|
376
|
-
import {
|
|
921
|
+
import {
|
|
922
|
+
buildProjectLogComment,
|
|
923
|
+
generatedPath,
|
|
924
|
+
githubAuthEnv,
|
|
925
|
+
loadDispatchPlan,
|
|
926
|
+
loadProjectContract,
|
|
927
|
+
loadTaskIndex,
|
|
928
|
+
normalizeProviderProfile,
|
|
929
|
+
orchestrationConfig,
|
|
930
|
+
setProjectAgentRole,
|
|
931
|
+
setProjectStatus,
|
|
932
|
+
writeJson,
|
|
933
|
+
} from "./runtime-lib.ts";
|
|
377
934
|
|
|
378
935
|
const orchestration = orchestrationConfig();
|
|
379
936
|
const dispatchPlan = loadDispatchPlan();
|
|
380
937
|
const taskIndex = loadTaskIndex();
|
|
938
|
+
const projectContract = loadProjectContract();
|
|
381
939
|
const executionDir = generatedPath("executions");
|
|
382
940
|
const commandTimeoutMs = Number(process.env.AGENTIC_AGENT_TIMEOUT_MS || 30000);
|
|
383
941
|
fs.mkdirSync(executionDir, { recursive: true });
|
|
@@ -391,17 +949,45 @@ function commentOnIssue(issueNumber, body) {
|
|
|
391
949
|
return spawnSync(
|
|
392
950
|
"gh",
|
|
393
951
|
["issue", "comment", String(issueNumber), "--repo", orchestration.github.repository.slug, "--body", body],
|
|
394
|
-
{ encoding: "utf-8" },
|
|
952
|
+
{ encoding: "utf-8", env: githubAuthEnv() },
|
|
395
953
|
);
|
|
396
954
|
}
|
|
397
955
|
|
|
956
|
+
function updateProjectState(record, statusKey) {
|
|
957
|
+
if (!projectContract || !record.project_item_id) return false;
|
|
958
|
+
try {
|
|
959
|
+
setProjectAgentRole(projectContract, record.project_item_id, record.agent_role || record.assigned_agent);
|
|
960
|
+
setProjectStatus(projectContract, record.project_item_id, statusKey);
|
|
961
|
+
record.project_status = statusKey;
|
|
962
|
+
return true;
|
|
963
|
+
} catch (error) {
|
|
964
|
+
record.warnings.push("project_status_update_failed: " + String(error?.message || error));
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
398
969
|
function buildPrompt(item, taskMeta) {
|
|
399
970
|
return [
|
|
400
971
|
\`You are the \${item.assigned_agent} agent working inside repository \${item.repository}.\`,
|
|
401
972
|
\`GitHub Project: \${item.project_title}\`,
|
|
402
973
|
\`Task id: \${item.task_id}\`,
|
|
403
974
|
\`Task title: \${item.title}\`,
|
|
975
|
+
item.agent_role ? \`Agent role: \${item.agent_role}\` : "",
|
|
976
|
+
item.task_origin ? \`Task origin: \${item.task_origin}\` : "",
|
|
977
|
+
item.priority ? \`Priority: \${item.priority}\` : "",
|
|
978
|
+
item.size ? \`Size: \${item.size}\` : "",
|
|
979
|
+
item.project_item_id ? \`Project item id: \${item.project_item_id}\` : "",
|
|
980
|
+
item.project_status ? \`Current project status: \${item.project_status}\` : "",
|
|
404
981
|
taskMeta?.source ? \`SDD source: \${taskMeta.source}\` : "",
|
|
982
|
+
Array.isArray(item.source_refs) && item.source_refs.length > 0
|
|
983
|
+
? \`Source refs: \${item.source_refs.join(", ")}\`
|
|
984
|
+
: "",
|
|
985
|
+
Array.isArray(item.target_paths_hint) && item.target_paths_hint.length > 0
|
|
986
|
+
? \`Target paths hint: \${item.target_paths_hint.join(", ")}\`
|
|
987
|
+
: "",
|
|
988
|
+
Array.isArray(item.suggested_commands) && item.suggested_commands.length > 0
|
|
989
|
+
? \`Suggested commands: \${item.suggested_commands.join(", ")}\`
|
|
990
|
+
: "",
|
|
405
991
|
"Follow the repository SDD workflow.",
|
|
406
992
|
"Implement the task directly in the working tree when changes are required.",
|
|
407
993
|
"Run relevant verification for the task.",
|
|
@@ -550,59 +1136,90 @@ async function runProvider(provider, prompt) {
|
|
|
550
1136
|
const executions = [];
|
|
551
1137
|
|
|
552
1138
|
async function main() {
|
|
553
|
-
for (const item of dispatchPlan.dispatch) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
1139
|
+
for (const item of dispatchPlan.dispatch) {
|
|
1140
|
+
const taskMeta = taskIndex.tasks.find((task) => task.id === item.task_id) || {};
|
|
1141
|
+
const executionPath = path.join(executionDir, \`\${item.task_id}.json\`);
|
|
1142
|
+
const prompt = buildPrompt(item, taskMeta);
|
|
1143
|
+
const startedAt = new Date().toISOString();
|
|
1144
|
+
|
|
1145
|
+
const record = {
|
|
1146
|
+
...item,
|
|
1147
|
+
issue_number: item.issue_number ?? taskMeta.issue_number ?? null,
|
|
1148
|
+
issue_url: item.issue_url ?? taskMeta.issue_url ?? null,
|
|
1149
|
+
project_item_id: item.project_item_id ?? taskMeta.project_item_id ?? null,
|
|
1150
|
+
project_status: item.project_status ?? taskMeta.project_status ?? null,
|
|
1151
|
+
agent_role: String(item.agent_role || taskMeta.agent_role || item.assigned_agent || ""),
|
|
1152
|
+
task_origin: item.task_origin || taskMeta.task_origin || "checklist",
|
|
1153
|
+
source_refs: Array.isArray(item.source_refs) ? item.source_refs : [],
|
|
1154
|
+
suggested_commands: Array.isArray(item.suggested_commands) ? item.suggested_commands : [],
|
|
1155
|
+
target_paths_hint: Array.isArray(item.target_paths_hint) ? item.target_paths_hint : [],
|
|
1156
|
+
priority: item.priority ?? null,
|
|
1157
|
+
size: item.size ?? null,
|
|
1158
|
+
started_at: startedAt,
|
|
1159
|
+
completed_at: null,
|
|
1160
|
+
prompt_path: path.relative(process.cwd(), executionPath.replace(/\\.json$/, ".prompt.txt")),
|
|
1161
|
+
execution_artifact: path.relative(process.cwd(), executionPath),
|
|
1162
|
+
adapter: "pending",
|
|
1163
|
+
execution_state: "running",
|
|
1164
|
+
exit_code: null,
|
|
1165
|
+
stdout: "",
|
|
1166
|
+
stderr: "",
|
|
1167
|
+
warnings: [],
|
|
1168
|
+
};
|
|
573
1169
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
1170
|
+
updateProjectState(record, "in_progress");
|
|
1171
|
+
fs.writeFileSync(executionPath.replace(/\\.json$/, ".prompt.txt"), prompt + "\\n");
|
|
1172
|
+
|
|
1173
|
+
const live = await runProvider(String(item.provider || ""), prompt);
|
|
1174
|
+
record.provider = normalizeProviderProfile(String(item.provider || ""));
|
|
1175
|
+
record.adapter = live.adapter;
|
|
1176
|
+
record.exit_code = live.result.status ?? null;
|
|
1177
|
+
record.stdout = String(live.result.stdout || "");
|
|
1178
|
+
record.stderr = String(live.result.stderr || "");
|
|
1179
|
+
record.execution_state = live.result.status === 0 ? "completed" : "failed";
|
|
1180
|
+
record.completed_at = new Date().toISOString();
|
|
1181
|
+
|
|
1182
|
+
if (record.execution_state === "completed") {
|
|
1183
|
+
updateProjectState(record, "in_review");
|
|
1184
|
+
} else if (record.project_item_id) {
|
|
1185
|
+
updateProjectState(record, "todo");
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
if (record.issue_number) {
|
|
1189
|
+
const comment = projectContract
|
|
1190
|
+
? buildProjectLogComment(projectContract, {
|
|
1191
|
+
agent_role: record.agent_role,
|
|
1192
|
+
status: record.execution_state === "completed" ? "in_review" : "in_progress",
|
|
1193
|
+
summary:
|
|
1194
|
+
"Dispatch finished via " +
|
|
1195
|
+
String(record.provider || "") +
|
|
1196
|
+
" (" +
|
|
1197
|
+
String(record.adapter || "") +
|
|
1198
|
+
"), state=" +
|
|
1199
|
+
String(record.execution_state || ""),
|
|
1200
|
+
next: record.execution_state === "completed" ? "quality" : "retry-or-manual-follow-up",
|
|
1201
|
+
})
|
|
1202
|
+
: [
|
|
1203
|
+
"Agentic-dev dispatch execution result",
|
|
1204
|
+
\`Task: \${record.task_id}\`,
|
|
1205
|
+
\`Provider: \${record.provider}\`,
|
|
1206
|
+
\`Adapter: \${record.adapter}\`,
|
|
1207
|
+
\`State: \${record.execution_state}\`,
|
|
1208
|
+
\`Execution artifact: \${record.execution_artifact}\`,
|
|
1209
|
+
].join("\\n");
|
|
1210
|
+
commentOnIssue(record.issue_number, comment);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
fs.writeFileSync(executionPath, JSON.stringify(record, null, 2) + "\\n");
|
|
1214
|
+
executions.push(record);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
writeJson(generatedPath("execution-journal.json"), {
|
|
1218
|
+
generated_at: new Date().toISOString(),
|
|
1219
|
+
executions,
|
|
1220
|
+
});
|
|
1221
|
+
console.log(\`executed=\${executions.length}\`);
|
|
1222
|
+
console.log(\`execution_journal=\${generatedPath("execution-journal.json")}\`);
|
|
606
1223
|
}
|
|
607
1224
|
|
|
608
1225
|
main().catch((error) => {
|
|
@@ -613,11 +1230,22 @@ main().catch((error) => {
|
|
|
613
1230
|
}
|
|
614
1231
|
function closeTasksScript() {
|
|
615
1232
|
return `#!/usr/bin/env node
|
|
616
|
-
import {
|
|
1233
|
+
import {
|
|
1234
|
+
generatedPath,
|
|
1235
|
+
gh,
|
|
1236
|
+
ghJson,
|
|
1237
|
+
loadExecutionJournal,
|
|
1238
|
+
loadProjectContract,
|
|
1239
|
+
orchestrationConfig,
|
|
1240
|
+
setProjectAgentRole,
|
|
1241
|
+
setProjectStatus,
|
|
1242
|
+
writeJson,
|
|
1243
|
+
} from "./runtime-lib.ts";
|
|
617
1244
|
|
|
618
1245
|
const orchestration = orchestrationConfig();
|
|
619
1246
|
const executionJournal = loadExecutionJournal();
|
|
620
|
-
const
|
|
1247
|
+
const projectContract = loadProjectContract();
|
|
1248
|
+
const issuesPayload = ghJson([
|
|
621
1249
|
"issue",
|
|
622
1250
|
"list",
|
|
623
1251
|
"--repo",
|
|
@@ -627,34 +1255,50 @@ const issues = ghJson([
|
|
|
627
1255
|
"--limit",
|
|
628
1256
|
"200",
|
|
629
1257
|
"--json",
|
|
630
|
-
"number
|
|
1258
|
+
"number",
|
|
631
1259
|
]);
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
const successfulTaskIds = new Set(
|
|
635
|
-
executionJournal.executions
|
|
636
|
-
.filter((execution) => execution.execution_state === "completed")
|
|
637
|
-
.map((execution) => execution.task_id),
|
|
1260
|
+
const openIssueNumbers = new Set(
|
|
1261
|
+
(Array.isArray(issuesPayload) ? issuesPayload : []).map((issue) => issue.number),
|
|
638
1262
|
);
|
|
1263
|
+
const closed = [];
|
|
1264
|
+
const warnings = [];
|
|
1265
|
+
|
|
1266
|
+
for (const execution of executionJournal.executions.filter((entry) => entry.execution_state === "completed")) {
|
|
1267
|
+
if (projectContract && execution.project_item_id) {
|
|
1268
|
+
try {
|
|
1269
|
+
setProjectAgentRole(projectContract, execution.project_item_id, execution.agent_role || execution.assigned_agent);
|
|
1270
|
+
setProjectStatus(projectContract, execution.project_item_id, "done");
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
warnings.push({
|
|
1273
|
+
id: execution.task_id,
|
|
1274
|
+
scope: "project-close",
|
|
1275
|
+
message: String(error?.message || error),
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
639
1279
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
1280
|
+
if (!execution.issue_number) continue;
|
|
1281
|
+
if (openIssueNumbers.has(execution.issue_number)) {
|
|
1282
|
+
gh([
|
|
1283
|
+
"issue",
|
|
1284
|
+
"close",
|
|
1285
|
+
String(execution.issue_number),
|
|
1286
|
+
"--repo",
|
|
1287
|
+
orchestration.github.repository.slug,
|
|
1288
|
+
"--comment",
|
|
1289
|
+
"Closed from SDD orchestration close pass.",
|
|
1290
|
+
]);
|
|
1291
|
+
}
|
|
1292
|
+
closed.push({
|
|
1293
|
+
id: execution.task_id,
|
|
1294
|
+
issue_number: execution.issue_number,
|
|
1295
|
+
project_item_id: execution.project_item_id ?? null,
|
|
1296
|
+
project_status: execution.project_item_id ? "done" : null,
|
|
1297
|
+
});
|
|
654
1298
|
}
|
|
655
1299
|
|
|
656
|
-
writeJson(generatedPath("closed-tasks.json"), { closed });
|
|
657
|
-
console.log(\`closed_candidates=\${
|
|
1300
|
+
writeJson(generatedPath("closed-tasks.json"), { closed, warnings });
|
|
1301
|
+
console.log(\`closed_candidates=\${closed.length}\`);
|
|
658
1302
|
`;
|
|
659
1303
|
}
|
|
660
1304
|
function serverScript() {
|