agentic-dev 0.2.22 → 0.2.24
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 +16 -8
- 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 +856 -195
- package/dist/lib/orchestration-assets.js.map +1 -1
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +206 -6
- 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() {
|
|
@@ -182,13 +511,30 @@ import fs from "node:fs";
|
|
|
182
511
|
import path from "node:path";
|
|
183
512
|
import { generatedPath, writeJson } from "./runtime-lib.ts";
|
|
184
513
|
|
|
514
|
+
const IGNORED_FILE_NAMES = new Set(["README.md", "INDEX.md"]);
|
|
515
|
+
const IGNORED_DIRECTORY_NAMES = new Set(["templates", "99_generated"]);
|
|
516
|
+
|
|
517
|
+
function shouldIncludeFile(file) {
|
|
518
|
+
const baseName = path.basename(file);
|
|
519
|
+
if (IGNORED_FILE_NAMES.has(baseName) || baseName.startsWith("_")) {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
const relativePath = path.relative(planRoot, file);
|
|
523
|
+
const segments = relativePath.split(path.sep).filter(Boolean);
|
|
524
|
+
return !segments.some((segment) => IGNORED_DIRECTORY_NAMES.has(segment));
|
|
525
|
+
}
|
|
526
|
+
|
|
185
527
|
function walk(root) {
|
|
186
528
|
if (!fs.existsSync(root)) return [];
|
|
187
529
|
const files = [];
|
|
188
530
|
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
189
531
|
const full = path.join(root, entry.name);
|
|
190
|
-
if (entry.isDirectory())
|
|
191
|
-
|
|
532
|
+
if (entry.isDirectory()) {
|
|
533
|
+
if (IGNORED_DIRECTORY_NAMES.has(entry.name)) continue;
|
|
534
|
+
files.push(...walk(full));
|
|
535
|
+
} else if (entry.name.endsWith(".md") && shouldIncludeFile(full)) {
|
|
536
|
+
files.push(full);
|
|
537
|
+
}
|
|
192
538
|
}
|
|
193
539
|
return files;
|
|
194
540
|
}
|
|
@@ -220,120 +566,323 @@ console.log(\`tasks=\${tasks.length}\`);
|
|
|
220
566
|
}
|
|
221
567
|
function syncProjectTasksScript() {
|
|
222
568
|
return `#!/usr/bin/env node
|
|
223
|
-
import {
|
|
569
|
+
import {
|
|
570
|
+
artifactContractPath,
|
|
571
|
+
ensureProjectItem,
|
|
572
|
+
findIssueForTask,
|
|
573
|
+
generatedPath,
|
|
574
|
+
gh,
|
|
575
|
+
ghJson,
|
|
576
|
+
hasArtifactBacklogKit,
|
|
577
|
+
issueNumberFromUrl,
|
|
578
|
+
issueUrl,
|
|
579
|
+
loadArtifactContract,
|
|
580
|
+
loadProjectContract,
|
|
581
|
+
loadRoleMap,
|
|
582
|
+
loadTaskCatalog,
|
|
583
|
+
loadTaskSyncState,
|
|
584
|
+
loadTaskIr,
|
|
585
|
+
normalizeTaskStatus,
|
|
586
|
+
orchestrationConfig,
|
|
587
|
+
parseTaskMeta,
|
|
588
|
+
projectContractAvailable,
|
|
589
|
+
projectContractPath,
|
|
590
|
+
projectItemIndex,
|
|
591
|
+
resolveAgentRole,
|
|
592
|
+
roleMapPath,
|
|
593
|
+
runArtifactBacklog,
|
|
594
|
+
setProjectAgentRole,
|
|
595
|
+
setProjectStatus,
|
|
596
|
+
taskIssueBody,
|
|
597
|
+
taskIssueTitle,
|
|
598
|
+
classifyAgent,
|
|
599
|
+
writeJson,
|
|
600
|
+
} from "./runtime-lib.ts";
|
|
224
601
|
|
|
225
602
|
const orchestration = orchestrationConfig();
|
|
603
|
+
const repository = orchestration?.github?.repository?.slug;
|
|
604
|
+
if (!repository) {
|
|
605
|
+
throw new Error("github repository slug is not configured in .agentic-dev/orchestration.json");
|
|
606
|
+
}
|
|
607
|
+
|
|
226
608
|
const taskIr = loadTaskIr();
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
609
|
+
const artifactContract = loadArtifactContract();
|
|
610
|
+
const projectContract = loadProjectContract();
|
|
611
|
+
const roleMap = loadRoleMap();
|
|
612
|
+
function loadIssues() {
|
|
613
|
+
const payload = ghJson([
|
|
614
|
+
"issue",
|
|
615
|
+
"list",
|
|
616
|
+
"--repo",
|
|
617
|
+
repository,
|
|
618
|
+
"--state",
|
|
619
|
+
"all",
|
|
620
|
+
"--limit",
|
|
621
|
+
"200",
|
|
622
|
+
"--json",
|
|
623
|
+
"number,title,state,body,url",
|
|
624
|
+
]);
|
|
625
|
+
return Array.isArray(payload) ? payload : [];
|
|
626
|
+
}
|
|
240
627
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
628
|
+
let existingIssues = loadIssues();
|
|
629
|
+
let itemIndex = projectContractAvailable(projectContract) ? projectItemIndex(projectContract) : new Map();
|
|
630
|
+
const syncResult = {
|
|
631
|
+
mode: "checklist",
|
|
632
|
+
artifact_backlog: null,
|
|
633
|
+
created: [],
|
|
634
|
+
updated: [],
|
|
635
|
+
reopened: [],
|
|
636
|
+
closed: [],
|
|
637
|
+
warnings: [],
|
|
638
|
+
project: {
|
|
639
|
+
enabled: projectContractAvailable(projectContract),
|
|
640
|
+
contract_path: projectContractAvailable(projectContract) ? projectContractPath() : null,
|
|
641
|
+
artifact_contract_path: artifactContract ? artifactContractPath() : null,
|
|
642
|
+
role_map_path: roleMapPath(),
|
|
643
|
+
},
|
|
644
|
+
tasks: [],
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
if (hasArtifactBacklogKit()) {
|
|
648
|
+
syncResult.mode = "artifact-backlog";
|
|
649
|
+
syncResult.artifact_backlog = runArtifactBacklog();
|
|
650
|
+
existingIssues = loadIssues();
|
|
651
|
+
itemIndex = projectContractAvailable(projectContract) ? projectItemIndex(projectContract) : new Map();
|
|
652
|
+
|
|
653
|
+
const catalog = loadTaskCatalog();
|
|
654
|
+
const syncState = loadTaskSyncState();
|
|
655
|
+
const catalogTasks = Array.isArray(catalog?.tasks) ? catalog.tasks : [];
|
|
656
|
+
|
|
657
|
+
for (const task of catalogTasks) {
|
|
658
|
+
const meta = parseTaskMeta(task.body);
|
|
659
|
+
const assignedAgent = resolveAgentRole(
|
|
660
|
+
roleMap,
|
|
661
|
+
String(task.agent_role || meta.agent_role || classifyAgent(task.title)),
|
|
662
|
+
);
|
|
663
|
+
const syncedIssue = syncState?.issues?.[task.key];
|
|
664
|
+
const mappedIssueNumber =
|
|
665
|
+
syncedIssue && typeof syncedIssue.number === "number" ? Number(syncedIssue.number) : null;
|
|
666
|
+
const found =
|
|
667
|
+
(mappedIssueNumber
|
|
668
|
+
? existingIssues.find((issue) => Number(issue?.number) === mappedIssueNumber)
|
|
669
|
+
: null) ||
|
|
670
|
+
findIssueForTask(existingIssues, task.key);
|
|
671
|
+
const taskStatus = normalizeTaskStatus(found?.state === "CLOSED" ? "closed" : "open");
|
|
672
|
+
const issueNumber = found?.number ?? mappedIssueNumber ?? null;
|
|
673
|
+
const issueLink = found?.url || (issueNumber ? issueUrl(repository, issueNumber) : null);
|
|
674
|
+
|
|
675
|
+
let projectItemId = null;
|
|
676
|
+
let projectStatus = null;
|
|
677
|
+
if (issueNumber && projectContractAvailable(projectContract)) {
|
|
678
|
+
try {
|
|
679
|
+
const item = ensureProjectItem(projectContract, Number(issueNumber), itemIndex);
|
|
680
|
+
projectItemId = item?.id || null;
|
|
681
|
+
if (projectItemId) {
|
|
682
|
+
setProjectAgentRole(projectContract, projectItemId, assignedAgent);
|
|
683
|
+
projectStatus = taskStatus === "closed" ? "done" : "todo";
|
|
684
|
+
setProjectStatus(projectContract, projectItemId, projectStatus);
|
|
685
|
+
}
|
|
686
|
+
} catch (error) {
|
|
687
|
+
syncResult.warnings.push({
|
|
688
|
+
id: task.key,
|
|
689
|
+
scope: "artifact-project-sync",
|
|
690
|
+
message: String(error?.message || error),
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
syncResult.tasks.push({
|
|
696
|
+
id: task.key,
|
|
697
|
+
title: task.title,
|
|
698
|
+
source:
|
|
699
|
+
Array.isArray(meta.source_refs) && meta.source_refs.length > 0
|
|
700
|
+
? String(meta.source_refs[0])
|
|
701
|
+
: "artifact:" + String(task.artifact_type || "task"),
|
|
702
|
+
status: taskStatus,
|
|
703
|
+
agent_role: assignedAgent,
|
|
704
|
+
issue_number: issueNumber,
|
|
705
|
+
issue_url: issueLink,
|
|
706
|
+
project_item_id: projectItemId,
|
|
707
|
+
project_status: projectStatus,
|
|
708
|
+
project_enabled: projectContractAvailable(projectContract),
|
|
709
|
+
task_origin: "artifact-backlog",
|
|
710
|
+
artifact_type: task.artifact_type || null,
|
|
711
|
+
source_refs: Array.isArray(meta.source_refs) ? meta.source_refs : [],
|
|
712
|
+
suggested_commands: Array.isArray(meta.suggested_commands) ? meta.suggested_commands : [],
|
|
713
|
+
target_paths_hint: Array.isArray(meta.target_paths_hint) ? meta.target_paths_hint : [],
|
|
714
|
+
priority: task.priority || null,
|
|
715
|
+
size: task.size || null,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (syncResult.tasks.length === 0) {
|
|
721
|
+
syncResult.mode = "checklist";
|
|
722
|
+
|
|
723
|
+
for (const task of taskIr.tasks) {
|
|
724
|
+
const assignedAgent = resolveAgentRole(roleMap, classifyAgent(task.title));
|
|
725
|
+
const issueTitle = taskIssueTitle(task);
|
|
726
|
+
const issueBody = taskIssueBody(task, assignedAgent);
|
|
727
|
+
const taskStatus = normalizeTaskStatus(task.status);
|
|
728
|
+
|
|
729
|
+
let found = findIssueForTask(existingIssues, task.id);
|
|
730
|
+
let issueNumber = found?.number ?? null;
|
|
731
|
+
let issueLink = found?.url || (issueNumber ? issueUrl(repository, issueNumber) : null);
|
|
732
|
+
|
|
733
|
+
if (!found && taskStatus === "open") {
|
|
734
|
+
const createdUrl = gh([
|
|
735
|
+
"issue",
|
|
736
|
+
"create",
|
|
737
|
+
"--repo",
|
|
738
|
+
repository,
|
|
739
|
+
"--title",
|
|
740
|
+
issueTitle,
|
|
741
|
+
"--body",
|
|
742
|
+
issueBody,
|
|
743
|
+
]);
|
|
744
|
+
issueNumber = issueNumberFromUrl(createdUrl);
|
|
745
|
+
issueLink = createdUrl || (issueNumber ? issueUrl(repository, issueNumber) : null);
|
|
746
|
+
found = {
|
|
747
|
+
number: issueNumber,
|
|
748
|
+
title: issueTitle,
|
|
749
|
+
state: "OPEN",
|
|
750
|
+
body: issueBody,
|
|
751
|
+
url: issueLink,
|
|
752
|
+
};
|
|
753
|
+
existingIssues.push(found);
|
|
754
|
+
syncResult.created.push({
|
|
755
|
+
id: task.id,
|
|
756
|
+
title: task.title,
|
|
757
|
+
issue_number: issueNumber,
|
|
758
|
+
issue_url: issueLink,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (found && taskStatus === "open" && found.state === "CLOSED") {
|
|
763
|
+
gh([
|
|
764
|
+
"issue",
|
|
765
|
+
"reopen",
|
|
766
|
+
String(found.number),
|
|
767
|
+
"--repo",
|
|
768
|
+
repository,
|
|
769
|
+
]);
|
|
770
|
+
found.state = "OPEN";
|
|
771
|
+
syncResult.reopened.push({ id: task.id, issue_number: found.number });
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (found && (String(found.title || "") !== issueTitle || String(found.body || "") !== issueBody)) {
|
|
775
|
+
gh([
|
|
776
|
+
"issue",
|
|
777
|
+
"edit",
|
|
778
|
+
String(found.number),
|
|
779
|
+
"--repo",
|
|
780
|
+
repository,
|
|
781
|
+
"--title",
|
|
782
|
+
issueTitle,
|
|
783
|
+
"--body",
|
|
784
|
+
issueBody,
|
|
785
|
+
]);
|
|
786
|
+
found.title = issueTitle;
|
|
787
|
+
found.body = issueBody;
|
|
788
|
+
syncResult.updated.push({ id: task.id, issue_number: found.number });
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (found && taskStatus === "closed" && found.state !== "CLOSED") {
|
|
792
|
+
gh([
|
|
793
|
+
"issue",
|
|
794
|
+
"close",
|
|
795
|
+
String(found.number),
|
|
796
|
+
"--repo",
|
|
797
|
+
repository,
|
|
798
|
+
"--comment",
|
|
799
|
+
"Closed from SDD task IR.",
|
|
800
|
+
]);
|
|
801
|
+
found.state = "CLOSED";
|
|
802
|
+
syncResult.closed.push({ id: task.id, issue_number: found.number });
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
issueNumber = found?.number ?? issueNumber ?? null;
|
|
806
|
+
issueLink = found?.url || issueLink || (issueNumber ? issueUrl(repository, issueNumber) : null);
|
|
807
|
+
|
|
808
|
+
let projectItemId = null;
|
|
809
|
+
let projectStatus = null;
|
|
810
|
+
if (issueNumber && projectContractAvailable(projectContract)) {
|
|
811
|
+
try {
|
|
812
|
+
const item = ensureProjectItem(projectContract, Number(issueNumber), itemIndex);
|
|
813
|
+
projectItemId = item?.id || null;
|
|
814
|
+
if (projectItemId) {
|
|
815
|
+
setProjectAgentRole(projectContract, projectItemId, assignedAgent);
|
|
816
|
+
projectStatus = taskStatus === "closed" ? "done" : "todo";
|
|
817
|
+
setProjectStatus(projectContract, projectItemId, projectStatus);
|
|
818
|
+
}
|
|
819
|
+
} catch (error) {
|
|
820
|
+
syncResult.warnings.push({
|
|
821
|
+
id: task.id,
|
|
822
|
+
scope: "project-sync",
|
|
823
|
+
message: String(error?.message || error),
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
syncResult.tasks.push({
|
|
829
|
+
id: task.id,
|
|
830
|
+
title: task.title,
|
|
831
|
+
source: task.source,
|
|
832
|
+
status: taskStatus,
|
|
833
|
+
agent_role: assignedAgent,
|
|
834
|
+
issue_number: issueNumber,
|
|
835
|
+
issue_url: issueLink,
|
|
836
|
+
project_item_id: projectItemId,
|
|
837
|
+
project_status: projectStatus,
|
|
838
|
+
project_enabled: projectContractAvailable(projectContract),
|
|
839
|
+
task_origin: "checklist",
|
|
840
|
+
});
|
|
258
841
|
}
|
|
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
842
|
}
|
|
283
843
|
|
|
284
844
|
writeJson(generatedPath("task-sync.json"), syncResult);
|
|
285
|
-
writeJson(generatedPath("task-index.json"), {
|
|
286
|
-
|
|
845
|
+
writeJson(generatedPath("task-index.json"), {
|
|
846
|
+
generated_at: new Date().toISOString(),
|
|
847
|
+
project: syncResult.project,
|
|
848
|
+
tasks: syncResult.tasks,
|
|
849
|
+
});
|
|
850
|
+
console.log(\`sync_mode=\${syncResult.mode}\`);
|
|
851
|
+
console.log(\`synced_tasks=\${syncResult.tasks.length}\`);
|
|
287
852
|
`;
|
|
288
853
|
}
|
|
289
854
|
function runQueueScript() {
|
|
290
855
|
return `#!/usr/bin/env node
|
|
291
|
-
import {
|
|
856
|
+
import {
|
|
857
|
+
classifyAgent,
|
|
858
|
+
generatedPath,
|
|
859
|
+
loadTaskIndex,
|
|
860
|
+
loadTaskIr,
|
|
861
|
+
normalizeTaskStatus,
|
|
862
|
+
orchestrationConfig,
|
|
863
|
+
pickProvider,
|
|
864
|
+
writeJson,
|
|
865
|
+
} from "./runtime-lib.ts";
|
|
292
866
|
|
|
293
867
|
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
|
-
}
|
|
868
|
+
const taskIndex = loadTaskIndex();
|
|
869
|
+
const indexedTasks = Array.isArray(taskIndex.tasks) ? taskIndex.tasks : [];
|
|
870
|
+
const tasks = indexedTasks.length > 0 ? indexedTasks : loadTaskIr().tasks;
|
|
330
871
|
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
872
|
+
const queue = tasks
|
|
873
|
+
.filter((task) => normalizeTaskStatus(task.status) === "open")
|
|
874
|
+
.map((task) => {
|
|
875
|
+
const assignedAgent = String(task.agent_role || classifyAgent(task.title));
|
|
876
|
+
return {
|
|
877
|
+
...task,
|
|
878
|
+
assigned_agent: assignedAgent,
|
|
879
|
+
agent_role: assignedAgent,
|
|
880
|
+
preferred_provider: pickProvider(task.title, orchestration.providers),
|
|
881
|
+
project_item_id: task.project_item_id ?? null,
|
|
882
|
+
project_status: task.project_status ?? null,
|
|
883
|
+
project_enabled: Boolean(task.project_enabled),
|
|
884
|
+
};
|
|
885
|
+
});
|
|
337
886
|
|
|
338
887
|
const outputPath = generatedPath("agent-queue.json");
|
|
339
888
|
writeJson(outputPath, { providers: orchestration.providers, queue });
|
|
@@ -343,19 +892,32 @@ console.log(\`queued=\${queue.length}\`);
|
|
|
343
892
|
}
|
|
344
893
|
function dispatchQueueScript() {
|
|
345
894
|
return `#!/usr/bin/env node
|
|
346
|
-
import { generatedPath, loadQueue, orchestrationConfig, writeJson } from "./runtime-lib.ts";
|
|
895
|
+
import { generatedPath, loadProjectContract, loadQueue, orchestrationConfig, writeJson } from "./runtime-lib.ts";
|
|
347
896
|
|
|
348
897
|
const orchestration = orchestrationConfig();
|
|
898
|
+
const projectContract = loadProjectContract();
|
|
349
899
|
const queuePayload = loadQueue();
|
|
350
900
|
|
|
351
901
|
const dispatch = queuePayload.queue.map((task) => ({
|
|
352
902
|
task_id: task.id,
|
|
353
903
|
title: task.title,
|
|
354
904
|
source: task.source,
|
|
905
|
+
source_refs: Array.isArray(task.source_refs) ? task.source_refs : [],
|
|
906
|
+
suggested_commands: Array.isArray(task.suggested_commands) ? task.suggested_commands : [],
|
|
907
|
+
target_paths_hint: Array.isArray(task.target_paths_hint) ? task.target_paths_hint : [],
|
|
908
|
+
priority: task.priority ?? null,
|
|
909
|
+
size: task.size ?? null,
|
|
910
|
+
task_origin: task.task_origin || "checklist",
|
|
355
911
|
assigned_agent: task.assigned_agent,
|
|
912
|
+
agent_role: task.agent_role || task.assigned_agent,
|
|
356
913
|
provider: task.preferred_provider,
|
|
357
914
|
repository: orchestration.github.repository.slug,
|
|
358
|
-
project_title: orchestration.github.project.title,
|
|
915
|
+
project_title: orchestration.github.project.title || projectContract?.project_title || "",
|
|
916
|
+
issue_number: task.issue_number ?? null,
|
|
917
|
+
issue_url: task.issue_url ?? null,
|
|
918
|
+
project_item_id: task.project_item_id ?? null,
|
|
919
|
+
project_status: task.project_status ?? null,
|
|
920
|
+
project_enabled: Boolean(task.project_enabled),
|
|
359
921
|
execution_state: "planned",
|
|
360
922
|
}));
|
|
361
923
|
|
|
@@ -373,11 +935,24 @@ function dispatchExecutorScript() {
|
|
|
373
935
|
import fs from "node:fs";
|
|
374
936
|
import path from "node:path";
|
|
375
937
|
import { spawnSync } from "node:child_process";
|
|
376
|
-
import {
|
|
938
|
+
import {
|
|
939
|
+
buildProjectLogComment,
|
|
940
|
+
generatedPath,
|
|
941
|
+
githubAuthEnv,
|
|
942
|
+
loadDispatchPlan,
|
|
943
|
+
loadProjectContract,
|
|
944
|
+
loadTaskIndex,
|
|
945
|
+
normalizeProviderProfile,
|
|
946
|
+
orchestrationConfig,
|
|
947
|
+
setProjectAgentRole,
|
|
948
|
+
setProjectStatus,
|
|
949
|
+
writeJson,
|
|
950
|
+
} from "./runtime-lib.ts";
|
|
377
951
|
|
|
378
952
|
const orchestration = orchestrationConfig();
|
|
379
953
|
const dispatchPlan = loadDispatchPlan();
|
|
380
954
|
const taskIndex = loadTaskIndex();
|
|
955
|
+
const projectContract = loadProjectContract();
|
|
381
956
|
const executionDir = generatedPath("executions");
|
|
382
957
|
const commandTimeoutMs = Number(process.env.AGENTIC_AGENT_TIMEOUT_MS || 30000);
|
|
383
958
|
fs.mkdirSync(executionDir, { recursive: true });
|
|
@@ -391,17 +966,45 @@ function commentOnIssue(issueNumber, body) {
|
|
|
391
966
|
return spawnSync(
|
|
392
967
|
"gh",
|
|
393
968
|
["issue", "comment", String(issueNumber), "--repo", orchestration.github.repository.slug, "--body", body],
|
|
394
|
-
{ encoding: "utf-8" },
|
|
969
|
+
{ encoding: "utf-8", env: githubAuthEnv() },
|
|
395
970
|
);
|
|
396
971
|
}
|
|
397
972
|
|
|
973
|
+
function updateProjectState(record, statusKey) {
|
|
974
|
+
if (!projectContract || !record.project_item_id) return false;
|
|
975
|
+
try {
|
|
976
|
+
setProjectAgentRole(projectContract, record.project_item_id, record.agent_role || record.assigned_agent);
|
|
977
|
+
setProjectStatus(projectContract, record.project_item_id, statusKey);
|
|
978
|
+
record.project_status = statusKey;
|
|
979
|
+
return true;
|
|
980
|
+
} catch (error) {
|
|
981
|
+
record.warnings.push("project_status_update_failed: " + String(error?.message || error));
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
398
986
|
function buildPrompt(item, taskMeta) {
|
|
399
987
|
return [
|
|
400
988
|
\`You are the \${item.assigned_agent} agent working inside repository \${item.repository}.\`,
|
|
401
989
|
\`GitHub Project: \${item.project_title}\`,
|
|
402
990
|
\`Task id: \${item.task_id}\`,
|
|
403
991
|
\`Task title: \${item.title}\`,
|
|
992
|
+
item.agent_role ? \`Agent role: \${item.agent_role}\` : "",
|
|
993
|
+
item.task_origin ? \`Task origin: \${item.task_origin}\` : "",
|
|
994
|
+
item.priority ? \`Priority: \${item.priority}\` : "",
|
|
995
|
+
item.size ? \`Size: \${item.size}\` : "",
|
|
996
|
+
item.project_item_id ? \`Project item id: \${item.project_item_id}\` : "",
|
|
997
|
+
item.project_status ? \`Current project status: \${item.project_status}\` : "",
|
|
404
998
|
taskMeta?.source ? \`SDD source: \${taskMeta.source}\` : "",
|
|
999
|
+
Array.isArray(item.source_refs) && item.source_refs.length > 0
|
|
1000
|
+
? \`Source refs: \${item.source_refs.join(", ")}\`
|
|
1001
|
+
: "",
|
|
1002
|
+
Array.isArray(item.target_paths_hint) && item.target_paths_hint.length > 0
|
|
1003
|
+
? \`Target paths hint: \${item.target_paths_hint.join(", ")}\`
|
|
1004
|
+
: "",
|
|
1005
|
+
Array.isArray(item.suggested_commands) && item.suggested_commands.length > 0
|
|
1006
|
+
? \`Suggested commands: \${item.suggested_commands.join(", ")}\`
|
|
1007
|
+
: "",
|
|
405
1008
|
"Follow the repository SDD workflow.",
|
|
406
1009
|
"Implement the task directly in the working tree when changes are required.",
|
|
407
1010
|
"Run relevant verification for the task.",
|
|
@@ -550,59 +1153,90 @@ async function runProvider(provider, prompt) {
|
|
|
550
1153
|
const executions = [];
|
|
551
1154
|
|
|
552
1155
|
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
|
-
|
|
1156
|
+
for (const item of dispatchPlan.dispatch) {
|
|
1157
|
+
const taskMeta = taskIndex.tasks.find((task) => task.id === item.task_id) || {};
|
|
1158
|
+
const executionPath = path.join(executionDir, \`\${item.task_id}.json\`);
|
|
1159
|
+
const prompt = buildPrompt(item, taskMeta);
|
|
1160
|
+
const startedAt = new Date().toISOString();
|
|
1161
|
+
|
|
1162
|
+
const record = {
|
|
1163
|
+
...item,
|
|
1164
|
+
issue_number: item.issue_number ?? taskMeta.issue_number ?? null,
|
|
1165
|
+
issue_url: item.issue_url ?? taskMeta.issue_url ?? null,
|
|
1166
|
+
project_item_id: item.project_item_id ?? taskMeta.project_item_id ?? null,
|
|
1167
|
+
project_status: item.project_status ?? taskMeta.project_status ?? null,
|
|
1168
|
+
agent_role: String(item.agent_role || taskMeta.agent_role || item.assigned_agent || ""),
|
|
1169
|
+
task_origin: item.task_origin || taskMeta.task_origin || "checklist",
|
|
1170
|
+
source_refs: Array.isArray(item.source_refs) ? item.source_refs : [],
|
|
1171
|
+
suggested_commands: Array.isArray(item.suggested_commands) ? item.suggested_commands : [],
|
|
1172
|
+
target_paths_hint: Array.isArray(item.target_paths_hint) ? item.target_paths_hint : [],
|
|
1173
|
+
priority: item.priority ?? null,
|
|
1174
|
+
size: item.size ?? null,
|
|
1175
|
+
started_at: startedAt,
|
|
1176
|
+
completed_at: null,
|
|
1177
|
+
prompt_path: path.relative(process.cwd(), executionPath.replace(/\\.json$/, ".prompt.txt")),
|
|
1178
|
+
execution_artifact: path.relative(process.cwd(), executionPath),
|
|
1179
|
+
adapter: "pending",
|
|
1180
|
+
execution_state: "running",
|
|
1181
|
+
exit_code: null,
|
|
1182
|
+
stdout: "",
|
|
1183
|
+
stderr: "",
|
|
1184
|
+
warnings: [],
|
|
1185
|
+
};
|
|
573
1186
|
|
|
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
|
-
|
|
1187
|
+
updateProjectState(record, "in_progress");
|
|
1188
|
+
fs.writeFileSync(executionPath.replace(/\\.json$/, ".prompt.txt"), prompt + "\\n");
|
|
1189
|
+
|
|
1190
|
+
const live = await runProvider(String(item.provider || ""), prompt);
|
|
1191
|
+
record.provider = normalizeProviderProfile(String(item.provider || ""));
|
|
1192
|
+
record.adapter = live.adapter;
|
|
1193
|
+
record.exit_code = live.result.status ?? null;
|
|
1194
|
+
record.stdout = String(live.result.stdout || "");
|
|
1195
|
+
record.stderr = String(live.result.stderr || "");
|
|
1196
|
+
record.execution_state = live.result.status === 0 ? "completed" : "failed";
|
|
1197
|
+
record.completed_at = new Date().toISOString();
|
|
1198
|
+
|
|
1199
|
+
if (record.execution_state === "completed") {
|
|
1200
|
+
updateProjectState(record, "in_review");
|
|
1201
|
+
} else if (record.project_item_id) {
|
|
1202
|
+
updateProjectState(record, "todo");
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (record.issue_number) {
|
|
1206
|
+
const comment = projectContract
|
|
1207
|
+
? buildProjectLogComment(projectContract, {
|
|
1208
|
+
agent_role: record.agent_role,
|
|
1209
|
+
status: record.execution_state === "completed" ? "in_review" : "in_progress",
|
|
1210
|
+
summary:
|
|
1211
|
+
"Dispatch finished via " +
|
|
1212
|
+
String(record.provider || "") +
|
|
1213
|
+
" (" +
|
|
1214
|
+
String(record.adapter || "") +
|
|
1215
|
+
"), state=" +
|
|
1216
|
+
String(record.execution_state || ""),
|
|
1217
|
+
next: record.execution_state === "completed" ? "quality" : "retry-or-manual-follow-up",
|
|
1218
|
+
})
|
|
1219
|
+
: [
|
|
1220
|
+
"Agentic-dev dispatch execution result",
|
|
1221
|
+
\`Task: \${record.task_id}\`,
|
|
1222
|
+
\`Provider: \${record.provider}\`,
|
|
1223
|
+
\`Adapter: \${record.adapter}\`,
|
|
1224
|
+
\`State: \${record.execution_state}\`,
|
|
1225
|
+
\`Execution artifact: \${record.execution_artifact}\`,
|
|
1226
|
+
].join("\\n");
|
|
1227
|
+
commentOnIssue(record.issue_number, comment);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
fs.writeFileSync(executionPath, JSON.stringify(record, null, 2) + "\\n");
|
|
1231
|
+
executions.push(record);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
writeJson(generatedPath("execution-journal.json"), {
|
|
1235
|
+
generated_at: new Date().toISOString(),
|
|
1236
|
+
executions,
|
|
1237
|
+
});
|
|
1238
|
+
console.log(\`executed=\${executions.length}\`);
|
|
1239
|
+
console.log(\`execution_journal=\${generatedPath("execution-journal.json")}\`);
|
|
606
1240
|
}
|
|
607
1241
|
|
|
608
1242
|
main().catch((error) => {
|
|
@@ -613,11 +1247,22 @@ main().catch((error) => {
|
|
|
613
1247
|
}
|
|
614
1248
|
function closeTasksScript() {
|
|
615
1249
|
return `#!/usr/bin/env node
|
|
616
|
-
import {
|
|
1250
|
+
import {
|
|
1251
|
+
generatedPath,
|
|
1252
|
+
gh,
|
|
1253
|
+
ghJson,
|
|
1254
|
+
loadExecutionJournal,
|
|
1255
|
+
loadProjectContract,
|
|
1256
|
+
orchestrationConfig,
|
|
1257
|
+
setProjectAgentRole,
|
|
1258
|
+
setProjectStatus,
|
|
1259
|
+
writeJson,
|
|
1260
|
+
} from "./runtime-lib.ts";
|
|
617
1261
|
|
|
618
1262
|
const orchestration = orchestrationConfig();
|
|
619
1263
|
const executionJournal = loadExecutionJournal();
|
|
620
|
-
const
|
|
1264
|
+
const projectContract = loadProjectContract();
|
|
1265
|
+
const issuesPayload = ghJson([
|
|
621
1266
|
"issue",
|
|
622
1267
|
"list",
|
|
623
1268
|
"--repo",
|
|
@@ -627,34 +1272,50 @@ const issues = ghJson([
|
|
|
627
1272
|
"--limit",
|
|
628
1273
|
"200",
|
|
629
1274
|
"--json",
|
|
630
|
-
"number
|
|
1275
|
+
"number",
|
|
631
1276
|
]);
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
const successfulTaskIds = new Set(
|
|
635
|
-
executionJournal.executions
|
|
636
|
-
.filter((execution) => execution.execution_state === "completed")
|
|
637
|
-
.map((execution) => execution.task_id),
|
|
1277
|
+
const openIssueNumbers = new Set(
|
|
1278
|
+
(Array.isArray(issuesPayload) ? issuesPayload : []).map((issue) => issue.number),
|
|
638
1279
|
);
|
|
1280
|
+
const closed = [];
|
|
1281
|
+
const warnings = [];
|
|
1282
|
+
|
|
1283
|
+
for (const execution of executionJournal.executions.filter((entry) => entry.execution_state === "completed")) {
|
|
1284
|
+
if (projectContract && execution.project_item_id) {
|
|
1285
|
+
try {
|
|
1286
|
+
setProjectAgentRole(projectContract, execution.project_item_id, execution.agent_role || execution.assigned_agent);
|
|
1287
|
+
setProjectStatus(projectContract, execution.project_item_id, "done");
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
warnings.push({
|
|
1290
|
+
id: execution.task_id,
|
|
1291
|
+
scope: "project-close",
|
|
1292
|
+
message: String(error?.message || error),
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
639
1296
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
1297
|
+
if (!execution.issue_number) continue;
|
|
1298
|
+
if (openIssueNumbers.has(execution.issue_number)) {
|
|
1299
|
+
gh([
|
|
1300
|
+
"issue",
|
|
1301
|
+
"close",
|
|
1302
|
+
String(execution.issue_number),
|
|
1303
|
+
"--repo",
|
|
1304
|
+
orchestration.github.repository.slug,
|
|
1305
|
+
"--comment",
|
|
1306
|
+
"Closed from SDD orchestration close pass.",
|
|
1307
|
+
]);
|
|
1308
|
+
}
|
|
1309
|
+
closed.push({
|
|
1310
|
+
id: execution.task_id,
|
|
1311
|
+
issue_number: execution.issue_number,
|
|
1312
|
+
project_item_id: execution.project_item_id ?? null,
|
|
1313
|
+
project_status: execution.project_item_id ? "done" : null,
|
|
1314
|
+
});
|
|
654
1315
|
}
|
|
655
1316
|
|
|
656
|
-
writeJson(generatedPath("closed-tasks.json"), { closed });
|
|
657
|
-
console.log(\`closed_candidates=\${
|
|
1317
|
+
writeJson(generatedPath("closed-tasks.json"), { closed, warnings });
|
|
1318
|
+
console.log(\`closed_candidates=\${closed.length}\`);
|
|
658
1319
|
`;
|
|
659
1320
|
}
|
|
660
1321
|
function serverScript() {
|