agentic-dev 0.2.13 → 0.2.15

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/lib/github.mjs DELETED
@@ -1,246 +0,0 @@
1
- import process from "node:process";
2
- import { spawnSync } from "node:child_process";
3
-
4
- function ghToken() {
5
- const envToken =
6
- process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.AGENTIC_GITHUB_TOKEN || "";
7
- if (envToken) {
8
- return envToken;
9
- }
10
-
11
- const result = spawnSync("gh", ["auth", "token"], {
12
- encoding: "utf-8",
13
- stdio: ["ignore", "pipe", "ignore"],
14
- });
15
- if (result.status === 0) {
16
- return (result.stdout || "").trim();
17
- }
18
- return "";
19
- }
20
-
21
- async function githubRequest(url, { method = "GET", body } = {}) {
22
- const token = ghToken();
23
- if (!token) {
24
- throw new Error("GitHub authentication is required. Set GH_TOKEN, GITHUB_TOKEN, or AGENTIC_GITHUB_TOKEN.");
25
- }
26
-
27
- const response = await fetch(url, {
28
- method,
29
- headers: {
30
- Accept: "application/vnd.github+json",
31
- "Content-Type": "application/json",
32
- "User-Agent": "agentic-dev",
33
- Authorization: `Bearer ${token}`,
34
- },
35
- body: body ? JSON.stringify(body) : undefined,
36
- });
37
-
38
- if (!response.ok) {
39
- const payload = await response.text();
40
- throw new Error(`GitHub API request failed (${response.status}): ${payload}`);
41
- }
42
-
43
- if (response.status === 204) {
44
- return null;
45
- }
46
- return response.json();
47
- }
48
-
49
- async function githubGraphql(query, variables = {}) {
50
- const token = ghToken();
51
- if (!token) {
52
- throw new Error("GitHub authentication is required. Set GH_TOKEN, GITHUB_TOKEN, or AGENTIC_GITHUB_TOKEN.");
53
- }
54
-
55
- const response = await fetch("https://api.github.com/graphql", {
56
- method: "POST",
57
- headers: {
58
- Accept: "application/vnd.github+json",
59
- "Content-Type": "application/json",
60
- "User-Agent": "agentic-dev",
61
- Authorization: `Bearer ${token}`,
62
- },
63
- body: JSON.stringify({ query, variables }),
64
- });
65
-
66
- const payload = await response.json();
67
- if (!response.ok || payload.errors) {
68
- throw new Error(`GitHub GraphQL request failed: ${JSON.stringify(payload.errors || payload)}`);
69
- }
70
- return payload.data;
71
- }
72
-
73
- export function normalizeRepoInput(input, { fallbackOwner = "say828", fallbackName = "agentic-service" } = {}) {
74
- const normalized = (input || "").trim();
75
- if (!normalized) {
76
- return {
77
- owner: fallbackOwner,
78
- name: fallbackName,
79
- slug: `${fallbackOwner}/${fallbackName}`,
80
- cloneUrl: `https://github.com/${fallbackOwner}/${fallbackName}.git`,
81
- htmlUrl: `https://github.com/${fallbackOwner}/${fallbackName}`,
82
- };
83
- }
84
-
85
- const withoutGit = normalized.replace(/\.git$/, "");
86
- const httpsMatch = withoutGit.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)$/);
87
- if (httpsMatch) {
88
- const [, owner, name] = httpsMatch;
89
- return {
90
- owner,
91
- name,
92
- slug: `${owner}/${name}`,
93
- cloneUrl: `https://github.com/${owner}/${name}.git`,
94
- htmlUrl: `https://github.com/${owner}/${name}`,
95
- };
96
- }
97
-
98
- const slugMatch = withoutGit.match(/^([^/]+)\/([^/]+)$/);
99
- if (slugMatch) {
100
- const [, owner, name] = slugMatch;
101
- return {
102
- owner,
103
- name,
104
- slug: `${owner}/${name}`,
105
- cloneUrl: `https://github.com/${owner}/${name}.git`,
106
- htmlUrl: `https://github.com/${owner}/${name}`,
107
- };
108
- }
109
-
110
- return {
111
- owner: fallbackOwner,
112
- name: withoutGit,
113
- slug: `${fallbackOwner}/${withoutGit}`,
114
- cloneUrl: `https://github.com/${fallbackOwner}/${withoutGit}.git`,
115
- htmlUrl: `https://github.com/${fallbackOwner}/${withoutGit}`,
116
- };
117
- }
118
-
119
- export async function getAuthenticatedViewer() {
120
- const data = await githubGraphql(`
121
- query ViewerIdentity {
122
- viewer {
123
- login
124
- id
125
- }
126
- }
127
- `);
128
- return data.viewer;
129
- }
130
-
131
- export async function ensureGitHubRepository(repoInput, { visibility = "private" } = {}) {
132
- const viewer = await getAuthenticatedViewer();
133
- const target = normalizeRepoInput(repoInput, {
134
- fallbackOwner: viewer.login,
135
- });
136
-
137
- try {
138
- const existing = await githubRequest(`https://api.github.com/repos/${target.owner}/${target.name}`);
139
- return {
140
- owner: existing.owner.login,
141
- name: existing.name,
142
- slug: existing.full_name,
143
- cloneUrl: existing.clone_url,
144
- htmlUrl: existing.html_url,
145
- created: false,
146
- };
147
- } catch (error) {
148
- const message = String(error.message || error);
149
- if (!message.includes("(404)")) {
150
- throw error;
151
- }
152
- }
153
-
154
- if (target.owner !== viewer.login) {
155
- throw new Error(
156
- `Repository ${target.slug} does not exist. Automatic creation currently supports only the authenticated user namespace (${viewer.login}).`,
157
- );
158
- }
159
-
160
- const created = await githubRequest("https://api.github.com/user/repos", {
161
- method: "POST",
162
- body: {
163
- name: target.name,
164
- private: visibility !== "public",
165
- auto_init: false,
166
- },
167
- });
168
-
169
- return {
170
- owner: created.owner.login,
171
- name: created.name,
172
- slug: created.full_name,
173
- cloneUrl: created.clone_url,
174
- htmlUrl: created.html_url,
175
- created: true,
176
- };
177
- }
178
-
179
- export async function ensureGitHubProject({ ownerLogin, title, mode = "create-if-missing" }) {
180
- const data = await githubGraphql(
181
- `
182
- query OwnerProjects($login: String!) {
183
- user(login: $login) {
184
- id
185
- projectsV2(first: 50) {
186
- nodes {
187
- id
188
- title
189
- number
190
- url
191
- }
192
- }
193
- }
194
- }
195
- `,
196
- { login: ownerLogin },
197
- );
198
-
199
- const owner = data.user;
200
- if (!owner) {
201
- throw new Error(`Unable to load GitHub projects for ${ownerLogin}.`);
202
- }
203
-
204
- const existing = owner.projectsV2.nodes.find((project) => project.title === title);
205
- if (existing) {
206
- return {
207
- id: existing.id,
208
- title: existing.title,
209
- number: existing.number,
210
- url: existing.url,
211
- created: false,
212
- };
213
- }
214
-
215
- if (mode !== "create-if-missing") {
216
- throw new Error(`GitHub project not found: ${title}`);
217
- }
218
-
219
- const created = await githubGraphql(
220
- `
221
- mutation CreateProject($ownerId: ID!, $title: String!) {
222
- createProjectV2(input: { ownerId: $ownerId, title: $title }) {
223
- projectV2 {
224
- id
225
- title
226
- number
227
- url
228
- }
229
- }
230
- }
231
- `,
232
- {
233
- ownerId: owner.id,
234
- title,
235
- },
236
- );
237
-
238
- const project = created.createProjectV2.projectV2;
239
- return {
240
- id: project.id,
241
- title: project.title,
242
- number: project.number,
243
- url: project.url,
244
- created: true,
245
- };
246
- }
@@ -1,249 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
-
4
- const SPECIALIZED_AGENTS = [
5
- { id: "architecture", description: "Drive boundaries, system shape, and migration decisions." },
6
- { id: "specs", description: "Translate SDD planning artifacts into actionable task structure." },
7
- { id: "runtime", description: "Own application/runtime implementation work." },
8
- { id: "ui", description: "Own screen and UI delivery tasks." },
9
- { id: "api", description: "Own contract and API work." },
10
- { id: "quality", description: "Run verification and regression closure." },
11
- { id: "gitops", description: "Own GitHub Projects, workflow automation, and delivery closure." },
12
- ];
13
-
14
- function repoRootDir() {
15
- return path.resolve(new URL("..", import.meta.url).pathname);
16
- }
17
-
18
- function copyRecursive(sourceRoot, destinationRoot) {
19
- fs.mkdirSync(destinationRoot, { recursive: true });
20
- for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
21
- const sourcePath = path.join(sourceRoot, entry.name);
22
- const destinationPath = path.join(destinationRoot, entry.name);
23
- if (entry.isDirectory()) {
24
- copyRecursive(sourcePath, destinationPath);
25
- } else {
26
- fs.copyFileSync(sourcePath, destinationPath);
27
- }
28
- }
29
- }
30
-
31
- function writeFile(filePath, content) {
32
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
33
- fs.writeFileSync(filePath, content);
34
- }
35
-
36
- function workflowYaml() {
37
- return `name: Agentic Orchestration
38
-
39
- on:
40
- push:
41
- paths:
42
- - "sdd/02_plan/**"
43
- - ".agentic-dev/orchestration.json"
44
- - ".agentic-dev/runtime/**"
45
- workflow_dispatch:
46
-
47
- jobs:
48
- orchestrate:
49
- runs-on: ubuntu-latest
50
- permissions:
51
- contents: read
52
- issues: write
53
- repository-projects: write
54
- steps:
55
- - name: Checkout
56
- uses: actions/checkout@v4
57
-
58
- - name: Setup Node
59
- uses: actions/setup-node@v4
60
- with:
61
- node-version: "20"
62
-
63
- - name: Build task IR from SDD planning
64
- run: node .agentic-dev/runtime/sdd_to_ir.mjs
65
-
66
- - name: Sync GitHub project tasks
67
- env:
68
- GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
69
- run: node .agentic-dev/runtime/sync_project_tasks.mjs
70
-
71
- - name: Plan multi-agent queue
72
- env:
73
- GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
74
- run: node .agentic-dev/runtime/run_multi_agent_queue.mjs
75
-
76
- - name: Close completed tasks
77
- env:
78
- GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
79
- run: node .agentic-dev/runtime/close_completed_tasks.mjs
80
- `;
81
- }
82
-
83
- function sddToIrScript() {
84
- return `#!/usr/bin/env node
85
- import fs from "node:fs";
86
- import path from "node:path";
87
-
88
- function walk(root) {
89
- if (!fs.existsSync(root)) return [];
90
- const files = [];
91
- for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
92
- const full = path.join(root, entry.name);
93
- if (entry.isDirectory()) files.push(...walk(full));
94
- else if (entry.name.endsWith(".md")) files.push(full);
95
- }
96
- return files;
97
- }
98
-
99
- function parseChecklist(markdown, source) {
100
- const tasks = [];
101
- const lines = markdown.split(/\\r?\\n/);
102
- for (const [index, line] of lines.entries()) {
103
- const match = line.match(/^- \\[( |x)\\] (.+)$/);
104
- if (!match) continue;
105
- const status = match[1] === "x" ? "closed" : "open";
106
- const title = match[2].trim();
107
- const id = \`\${path.basename(source, ".md")}:\${index + 1}\`;
108
- tasks.push({ id, title, status, source });
109
- }
110
- return tasks;
111
- }
112
-
113
- const planRoot = path.resolve("sdd/02_plan");
114
- const files = walk(planRoot);
115
- const tasks = files.flatMap((file) => parseChecklist(fs.readFileSync(file, "utf-8"), path.relative(process.cwd(), file)));
116
- const outputDir = path.resolve(".agentic-dev/generated");
117
- fs.mkdirSync(outputDir, { recursive: true });
118
- const outputPath = path.join(outputDir, "task-ir.json");
119
- fs.writeFileSync(outputPath, JSON.stringify({ generated_at: new Date().toISOString(), tasks }, null, 2) + "\\n");
120
- console.log(\`task_ir=\${outputPath}\`);
121
- console.log(\`tasks=\${tasks.length}\`);
122
- `;
123
- }
124
-
125
- function syncProjectTasksScript() {
126
- return `#!/usr/bin/env node
127
- import fs from "node:fs";
128
- import { execFileSync } from "node:child_process";
129
-
130
- const orchestration = JSON.parse(fs.readFileSync(".agentic-dev/orchestration.json", "utf-8"));
131
- const taskIr = JSON.parse(fs.readFileSync(".agentic-dev/generated/task-ir.json", "utf-8"));
132
-
133
- function ghJson(args) {
134
- return JSON.parse(execFileSync("gh", args, { encoding: "utf-8" }));
135
- }
136
-
137
- function gh(args) {
138
- return execFileSync("gh", args, { encoding: "utf-8" }).trim();
139
- }
140
-
141
- const existingIssues = ghJson(["issue", "list", "--repo", orchestration.github.repository.slug, "--state", "all", "--limit", "200", "--json", "number,title,state"]);
142
-
143
- for (const task of taskIr.tasks) {
144
- const issueTitle = \`[agentic-task] \${task.id} \${task.title}\`;
145
- const found = existingIssues.find((issue) => issue.title.startsWith(\`[agentic-task] \${task.id} \`));
146
- if (!found && task.status === "open") {
147
- gh(["issue", "create", "--repo", orchestration.github.repository.slug, "--title", issueTitle, "--body", \`SDD source: \${task.source}\\n\\nManaged by agentic-dev orchestration.\`, "--label", "agentic-task"]);
148
- }
149
- if (found && task.status === "closed" && found.state !== "CLOSED") {
150
- gh(["issue", "close", String(found.number), "--repo", orchestration.github.repository.slug, "--comment", "Closed from SDD task IR."]);
151
- }
152
- }
153
-
154
- console.log(\`synced_tasks=\${taskIr.tasks.length}\`);
155
- `;
156
- }
157
-
158
- function runQueueScript() {
159
- return `#!/usr/bin/env node
160
- import fs from "node:fs";
161
-
162
- const orchestration = JSON.parse(fs.readFileSync(".agentic-dev/orchestration.json", "utf-8"));
163
- const taskIr = JSON.parse(fs.readFileSync(".agentic-dev/generated/task-ir.json", "utf-8"));
164
-
165
- function classifyAgent(title) {
166
- const lower = title.toLowerCase();
167
- if (lower.includes("api") || lower.includes("contract")) return "api";
168
- if (lower.includes("screen") || lower.includes("ui")) return "ui";
169
- if (lower.includes("verify") || lower.includes("test") || lower.includes("proof")) return "quality";
170
- if (lower.includes("workflow") || lower.includes("project") || lower.includes("github")) return "gitops";
171
- if (lower.includes("arch") || lower.includes("boundary") || lower.includes("structure")) return "architecture";
172
- return "runtime";
173
- }
174
-
175
- const openTasks = taskIr.tasks.filter((task) => task.status === "open");
176
- const queue = openTasks.map((task) => ({
177
- ...task,
178
- assigned_agent: classifyAgent(task.title),
179
- }));
180
-
181
- const outputPath = ".agentic-dev/generated/agent-queue.json";
182
- fs.writeFileSync(outputPath, JSON.stringify({ providers: orchestration.providers, queue }, null, 2) + "\\n");
183
- console.log(\`agent_queue=\${outputPath}\`);
184
- console.log(\`queued=\${queue.length}\`);
185
- `;
186
- }
187
-
188
- function closeTasksScript() {
189
- return `#!/usr/bin/env node
190
- import fs from "node:fs";
191
- import { execFileSync } from "node:child_process";
192
-
193
- const orchestration = JSON.parse(fs.readFileSync(".agentic-dev/orchestration.json", "utf-8"));
194
- const taskIr = JSON.parse(fs.readFileSync(".agentic-dev/generated/task-ir.json", "utf-8"));
195
-
196
- const closedIds = new Set(taskIr.tasks.filter((task) => task.status === "closed").map((task) => task.id));
197
- const issues = JSON.parse(execFileSync("gh", ["issue", "list", "--repo", orchestration.github.repository.slug, "--state", "open", "--limit", "200", "--json", "number,title"], { encoding: "utf-8" }));
198
-
199
- for (const issue of issues) {
200
- const match = issue.title.match(/^\\[agentic-task\\] ([^ ]+) /);
201
- if (!match) continue;
202
- if (!closedIds.has(match[1])) continue;
203
- execFileSync("gh", ["issue", "close", String(issue.number), "--repo", orchestration.github.repository.slug, "--comment", "Closed from SDD orchestration close pass."], { encoding: "utf-8" });
204
- }
205
-
206
- console.log(\`closed_candidates=\${closedIds.size}\`);
207
- `;
208
- }
209
-
210
- export function installSharedAgentAssets(destinationRoot) {
211
- const root = repoRootDir();
212
- copyRecursive(path.join(root, ".agent"), path.join(destinationRoot, ".agent"));
213
- copyRecursive(path.join(root, ".claude"), path.join(destinationRoot, ".claude"));
214
- copyRecursive(path.join(root, ".codex"), path.join(destinationRoot, ".codex"));
215
- }
216
-
217
- export function installOrchestrationAssets(destinationRoot) {
218
- writeFile(path.join(destinationRoot, ".github/workflows/agentic-orchestration.yml"), workflowYaml());
219
- writeFile(path.join(destinationRoot, ".agentic-dev/runtime/sdd_to_ir.mjs"), sddToIrScript());
220
- writeFile(path.join(destinationRoot, ".agentic-dev/runtime/sync_project_tasks.mjs"), syncProjectTasksScript());
221
- writeFile(path.join(destinationRoot, ".agentic-dev/runtime/run_multi_agent_queue.mjs"), runQueueScript());
222
- writeFile(path.join(destinationRoot, ".agentic-dev/runtime/close_completed_tasks.mjs"), closeTasksScript());
223
- }
224
-
225
- export function buildOrchestrationConfig(setupSelections = {}) {
226
- return {
227
- project_name: setupSelections.projectName || "",
228
- specialized_agents: SPECIALIZED_AGENTS,
229
- providers: Array.isArray(setupSelections.providerProfiles) ? setupSelections.providerProfiles : [],
230
- github: {
231
- repository: setupSelections.githubRepository || {},
232
- project: setupSelections.githubProject || {},
233
- project_mode: setupSelections.githubProjectMode || "create-if-missing",
234
- },
235
- workflow: {
236
- ir_output: ".agentic-dev/generated/task-ir.json",
237
- queue_output: ".agentic-dev/generated/agent-queue.json",
238
- workflow_file: ".github/workflows/agentic-orchestration.yml",
239
- },
240
- };
241
- }
242
-
243
- export function writeOrchestrationConfig(destinationRoot, setupSelections = {}) {
244
- const config = buildOrchestrationConfig(setupSelections);
245
- writeFile(
246
- path.join(destinationRoot, ".agentic-dev/orchestration.json"),
247
- `${JSON.stringify(config, null, 2)}\n`,
248
- );
249
- }