@xn-intenton-z2a/agentic-lib 7.4.8 → 7.4.9
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/{src → .github}/agents/agent-apply-fix.md +10 -0
- package/{src → .github}/agents/agent-director.md +10 -0
- package/{src → .github}/agents/agent-discovery.md +8 -0
- package/{src → .github}/agents/agent-discussion-bot.md +9 -0
- package/{src → .github}/agents/agent-issue-resolution.md +12 -0
- package/{src → .github}/agents/agent-iterate.md +8 -0
- package/{src → .github}/agents/agent-maintain-features.md +8 -0
- package/{src → .github}/agents/agent-maintain-library.md +7 -0
- package/{src → .github}/agents/agent-review-issue.md +8 -0
- package/{src → .github}/agents/agent-supervisor.md +9 -0
- package/.github/workflows/agentic-lib-test.yml +4 -2
- package/.github/workflows/agentic-lib-workflow.yml +70 -26
- package/README.md +5 -7
- package/agentic-lib.toml +16 -38
- package/bin/agentic-lib.js +49 -60
- package/package.json +3 -4
- package/src/actions/agentic-step/action.yml +1 -1
- package/src/actions/agentic-step/copilot.js +0 -5
- package/src/actions/agentic-step/index.js +8 -1
- package/src/actions/agentic-step/logging.js +14 -2
- package/src/actions/agentic-step/tasks/direct.js +86 -65
- package/src/actions/agentic-step/tasks/discussions.js +198 -264
- package/src/actions/agentic-step/tasks/enhance-issue.js +84 -33
- package/src/actions/agentic-step/tasks/fix-code.js +111 -57
- package/src/actions/agentic-step/tasks/maintain-features.js +69 -52
- package/src/actions/agentic-step/tasks/maintain-library.js +57 -19
- package/src/actions/agentic-step/tasks/resolve-issue.js +43 -18
- package/src/actions/agentic-step/tasks/review-issue.js +117 -117
- package/src/actions/agentic-step/tasks/supervise.js +140 -151
- package/src/actions/agentic-step/tasks/transform.js +106 -258
- package/src/copilot/agents.js +2 -2
- package/src/copilot/config.js +2 -18
- package/src/copilot/{hybrid-session.js → copilot-session.js} +39 -7
- package/src/copilot/github-tools.js +514 -0
- package/src/copilot/guards.js +1 -1
- package/src/copilot/session.js +0 -141
- package/src/copilot/tools.js +4 -0
- package/src/iterate.js +1 -1
- package/src/scripts/push-to-logs.sh +1 -1
- package/src/seeds/zero-SCREENSHOT_INDEX.png +0 -0
- package/src/seeds/zero-package.json +1 -1
- package/src/agents/agentic-lib.yml +0 -66
- package/src/copilot/context.js +0 -457
- package/src/mcp/server.js +0 -830
- /package/{src → .github}/agents/agent-ready-issue.md +0 -0
|
@@ -2,26 +2,45 @@
|
|
|
2
2
|
// Copyright (C) 2025-2026 Polycode Limited
|
|
3
3
|
// tasks/review-issue.js — Review issues and close resolved ones
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
// if
|
|
7
|
-
// Supports batch mode: when no issueNumber is provided, reviews up to 3 issues.
|
|
5
|
+
// Uses runCopilotSession with lean prompts: the model reads source files
|
|
6
|
+
// via tools to determine if issues have been resolved.
|
|
8
7
|
|
|
9
8
|
import * as core from "@actions/core";
|
|
10
|
-
import {
|
|
9
|
+
import { existsSync, readdirSync, statSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
import { extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
|
|
12
|
+
import { runCopilotSession } from "../../../copilot/copilot-session.js";
|
|
13
|
+
import { createGitHubTools, createGitTools } from "../../../copilot/github-tools.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build a file listing summary (names + sizes, not content).
|
|
17
|
+
*/
|
|
18
|
+
function buildFileListing(dirPath, extensions) {
|
|
19
|
+
if (!dirPath || !existsSync(dirPath)) return [];
|
|
20
|
+
const exts = Array.isArray(extensions) ? extensions : [extensions];
|
|
21
|
+
try {
|
|
22
|
+
const files = readdirSync(dirPath, { recursive: true });
|
|
23
|
+
return files
|
|
24
|
+
.filter((f) => exts.some((ext) => String(f).endsWith(ext)))
|
|
25
|
+
.map((f) => {
|
|
26
|
+
const fullPath = join(dirPath, String(f));
|
|
27
|
+
try {
|
|
28
|
+
const stat = statSync(fullPath);
|
|
29
|
+
return `${f} (~${Math.round(stat.size / 40)} lines)`;
|
|
30
|
+
} catch {
|
|
31
|
+
return String(f);
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.slice(0, 30);
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
11
39
|
|
|
12
40
|
/**
|
|
13
41
|
* Review a single issue against the current codebase.
|
|
14
|
-
*
|
|
15
|
-
* @param {Object} params
|
|
16
|
-
* @param {Object} params.octokit - GitHub API client
|
|
17
|
-
* @param {Object} params.repo - { owner, repo }
|
|
18
|
-
* @param {Object} params.config - Loaded config
|
|
19
|
-
* @param {number} params.targetIssueNumber - Issue number to review
|
|
20
|
-
* @param {string} params.instructions - Agent instructions
|
|
21
|
-
* @param {string} params.model - Model name
|
|
22
|
-
* @returns {Promise<Object>} Result with outcome, tokensUsed, model
|
|
23
42
|
*/
|
|
24
|
-
async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, instructions, model, tuning: t }) {
|
|
43
|
+
async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, instructions, model, tuning: t, logFilePath, screenshotFilePath }) {
|
|
25
44
|
const { data: issue } = await octokit.rest.issues.get({
|
|
26
45
|
...repo,
|
|
27
46
|
issue_number: Number(targetIssueNumber),
|
|
@@ -31,52 +50,14 @@ async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, ins
|
|
|
31
50
|
return { outcome: "nop", details: `Issue #${targetIssueNumber} is already closed` };
|
|
32
51
|
}
|
|
33
52
|
|
|
34
|
-
const sourceFiles =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
recursive: true,
|
|
38
|
-
sortByMtime: true,
|
|
39
|
-
clean: true,
|
|
40
|
-
outline: true,
|
|
41
|
-
});
|
|
42
|
-
const testFiles = scanDirectory(config.paths.tests.path, [".test.js", ".test.ts"], {
|
|
43
|
-
contentLimit: t.testContent || t.sourceContent || 5000,
|
|
44
|
-
fileLimit: t.sourceScan || 20,
|
|
45
|
-
recursive: true,
|
|
46
|
-
sortByMtime: true,
|
|
47
|
-
clean: true,
|
|
48
|
-
});
|
|
49
|
-
const webFiles = scanDirectory(config.paths.web?.path || "src/web/", [".html", ".css", ".js"], {
|
|
50
|
-
fileLimit: t.sourceScan || 20,
|
|
51
|
-
contentLimit: t.sourceContent || 5000,
|
|
52
|
-
recursive: true,
|
|
53
|
-
sortByMtime: true,
|
|
54
|
-
clean: true,
|
|
55
|
-
});
|
|
56
|
-
const docsFiles = scanDirectory(config.paths.documentation?.path || "docs/", [".md"], {
|
|
57
|
-
fileLimit: t.featuresScan || 10,
|
|
58
|
-
contentLimit: t.documentSummary || 2000,
|
|
59
|
-
});
|
|
53
|
+
const sourceFiles = buildFileListing(config.paths.source.path, [".js", ".ts"]);
|
|
54
|
+
const testFiles = buildFileListing(config.paths.tests.path, [".js", ".ts"]);
|
|
55
|
+
const webFiles = buildFileListing(config.paths.web?.path || "src/web/", [".html", ".css", ".js"]);
|
|
60
56
|
|
|
61
57
|
const agentInstructions = instructions || "Review whether this issue has been resolved by the current codebase.";
|
|
62
58
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const initTimestamp = config.init?.timestamp || null;
|
|
67
|
-
const since = initTimestamp || new Date(Date.now() - 7 * 86400000).toISOString();
|
|
68
|
-
const { data: commits } = await octokit.rest.repos.listCommits({
|
|
69
|
-
...repo,
|
|
70
|
-
sha: "main",
|
|
71
|
-
since,
|
|
72
|
-
per_page: 10,
|
|
73
|
-
});
|
|
74
|
-
recentCommitsSummary = commits.map((c) => {
|
|
75
|
-
const msg = c.commit.message.split("\n")[0];
|
|
76
|
-
const sha = c.sha.substring(0, 7);
|
|
77
|
-
return `- [${sha}] ${msg} (${c.commit.author?.date || ""})`;
|
|
78
|
-
});
|
|
79
|
-
} catch { /* ignore */ }
|
|
59
|
+
// Shared mutable state to capture the verdict
|
|
60
|
+
const verdictResult = { verdict: "", resolved: false };
|
|
80
61
|
|
|
81
62
|
const prompt = [
|
|
82
63
|
"## Instructions",
|
|
@@ -85,53 +66,76 @@ async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, ins
|
|
|
85
66
|
`## Issue #${targetIssueNumber}: ${issue.title}`,
|
|
86
67
|
issue.body || "(no description)",
|
|
87
68
|
"",
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
""
|
|
91
|
-
|
|
92
|
-
...testFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
93
|
-
"",
|
|
94
|
-
...(webFiles.length > 0
|
|
95
|
-
? [
|
|
96
|
-
`## Website Files (${webFiles.length} files)`,
|
|
97
|
-
"The website in `src/web/` uses the JS library.",
|
|
98
|
-
...webFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
99
|
-
"",
|
|
100
|
-
]
|
|
101
|
-
: []),
|
|
102
|
-
...(docsFiles.length > 0
|
|
103
|
-
? [`## Documentation (${docsFiles.length} files)`, ...docsFiles.map((f) => `- ${f.name}`), ""]
|
|
104
|
-
: []),
|
|
105
|
-
...(recentCommitsSummary.length > 0
|
|
106
|
-
? [`## Recent Commits (since init)`, ...recentCommitsSummary, ""]
|
|
107
|
-
: []),
|
|
108
|
-
config.configToml ? `## Configuration (agentic-lib.toml)\n\`\`\`toml\n${config.configToml}\n\`\`\`` : "",
|
|
109
|
-
config.packageJson ? `## Dependencies (package.json)\n\`\`\`json\n${config.packageJson}\n\`\`\`` : "",
|
|
69
|
+
"## Repository Structure",
|
|
70
|
+
`Source files (${sourceFiles.length}): ${sourceFiles.join(", ") || "none"}`,
|
|
71
|
+
`Test files (${testFiles.length}): ${testFiles.join(", ") || "none"}`,
|
|
72
|
+
...(webFiles.length > 0 ? [`Website files (${webFiles.length}): ${webFiles.join(", ")}`] : []),
|
|
110
73
|
"",
|
|
111
74
|
"## Your Task",
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
|
|
115
|
-
|
|
75
|
+
"Read the relevant source and test files using read_file to determine if this issue has been resolved.",
|
|
76
|
+
"Use git_diff or git_status for additional context if needed.",
|
|
77
|
+
"Then call report_verdict with your determination.",
|
|
78
|
+
"",
|
|
79
|
+
"**You MUST call report_verdict exactly once.**",
|
|
116
80
|
].join("\n");
|
|
117
81
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
82
|
+
const systemPrompt =
|
|
83
|
+
"You are a code reviewer determining if a GitHub issue has been resolved by the current codebase. Read the source files, analyze them against the issue requirements, and report your verdict." +
|
|
84
|
+
NARRATIVE_INSTRUCTION;
|
|
85
|
+
|
|
86
|
+
const createTools = (defineTool, _wp, logger) => {
|
|
87
|
+
const ghTools = createGitHubTools(octokit, repo, defineTool, logger);
|
|
88
|
+
const gitTools = createGitTools(defineTool, logger);
|
|
89
|
+
|
|
90
|
+
const reportVerdict = defineTool("report_verdict", {
|
|
91
|
+
description: "Report whether the issue is resolved or still open. Call this exactly once.",
|
|
92
|
+
parameters: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
resolved: { type: "boolean", description: "true if the issue is resolved, false if still open" },
|
|
96
|
+
reason: { type: "string", description: "Explanation of why the issue is or is not resolved" },
|
|
97
|
+
files_reviewed: { type: "number", description: "Number of source files read during review" },
|
|
98
|
+
},
|
|
99
|
+
required: ["resolved", "reason"],
|
|
100
|
+
},
|
|
101
|
+
handler: async ({ resolved, reason, files_reviewed }) => {
|
|
102
|
+
verdictResult.verdict = `${resolved ? "RESOLVED" : "OPEN"}: ${reason}`;
|
|
103
|
+
verdictResult.resolved = resolved;
|
|
104
|
+
verdictResult.filesReviewed = files_reviewed || 0;
|
|
105
|
+
return { textResultForLlm: `Verdict recorded: ${resolved ? "RESOLVED" : "OPEN"}` };
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return [...ghTools, ...gitTools, reportVerdict];
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const attachments = [];
|
|
113
|
+
if (logFilePath) attachments.push({ type: "file", path: logFilePath });
|
|
114
|
+
if (screenshotFilePath) attachments.push({ type: "file", path: screenshotFilePath });
|
|
115
|
+
|
|
116
|
+
const result = await runCopilotSession({
|
|
117
|
+
workspacePath: process.cwd(),
|
|
125
118
|
model,
|
|
126
|
-
systemMessage: "You are a code reviewer determining if GitHub issues have been resolved.",
|
|
127
|
-
prompt,
|
|
128
|
-
writablePaths: [],
|
|
129
119
|
tuning: t,
|
|
120
|
+
agentPrompt: systemPrompt,
|
|
121
|
+
userPrompt: prompt,
|
|
122
|
+
writablePaths: [],
|
|
123
|
+
createTools,
|
|
124
|
+
attachments,
|
|
125
|
+
excludedTools: ["write_file", "run_command", "run_tests", "dispatch_workflow", "close_issue", "label_issue", "post_discussion_comment"],
|
|
126
|
+
logger: { info: core.info, warning: core.warning, error: core.error, debug: core.debug },
|
|
130
127
|
});
|
|
131
128
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
const tokensUsed = result.tokensIn + result.tokensOut;
|
|
130
|
+
|
|
131
|
+
// If the model didn't call report_verdict, try to infer from the message
|
|
132
|
+
if (!verdictResult.verdict && result.agentMessage) {
|
|
133
|
+
const normalised = result.agentMessage.replace(/^[*_`#>\s-]+/, "").toUpperCase();
|
|
134
|
+
verdictResult.resolved = normalised.startsWith("RESOLVED");
|
|
135
|
+
verdictResult.verdict = result.agentMessage.substring(0, 500);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (verdictResult.resolved) {
|
|
135
139
|
await octokit.rest.issues.createComment({
|
|
136
140
|
...repo,
|
|
137
141
|
issue_number: Number(targetIssueNumber),
|
|
@@ -140,10 +144,9 @@ async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, ins
|
|
|
140
144
|
"",
|
|
141
145
|
`**Task:** review-issue`,
|
|
142
146
|
`**Model:** ${model}`,
|
|
143
|
-
`**
|
|
144
|
-
`**Test files reviewed:** ${testFiles.length}`,
|
|
147
|
+
`**Files reviewed:** ${verdictResult.filesReviewed || "unknown"}`,
|
|
145
148
|
"",
|
|
146
|
-
verdict,
|
|
149
|
+
verdictResult.verdict,
|
|
147
150
|
].join("\n"),
|
|
148
151
|
});
|
|
149
152
|
await octokit.rest.issues.update({
|
|
@@ -156,25 +159,25 @@ async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, ins
|
|
|
156
159
|
return {
|
|
157
160
|
outcome: "issue-closed",
|
|
158
161
|
tokensUsed,
|
|
159
|
-
inputTokens,
|
|
160
|
-
outputTokens,
|
|
161
|
-
cost,
|
|
162
|
+
inputTokens: result.tokensIn,
|
|
163
|
+
outputTokens: result.tokensOut,
|
|
164
|
+
cost: 0,
|
|
162
165
|
model,
|
|
163
|
-
details: `Closed issue #${targetIssueNumber}: ${verdict.substring(0, 200)}`,
|
|
164
|
-
narrative: `Reviewed issue #${targetIssueNumber} and closed it as resolved.`,
|
|
166
|
+
details: `Closed issue #${targetIssueNumber}: ${verdictResult.verdict.substring(0, 200)}`,
|
|
167
|
+
narrative: result.narrative || `Reviewed issue #${targetIssueNumber} and closed it as resolved.`,
|
|
165
168
|
};
|
|
166
169
|
}
|
|
167
170
|
|
|
168
|
-
core.info(`Issue #${targetIssueNumber} still open: ${verdict.substring(0, 100)}`);
|
|
171
|
+
core.info(`Issue #${targetIssueNumber} still open: ${verdictResult.verdict.substring(0, 100)}`);
|
|
169
172
|
return {
|
|
170
173
|
outcome: "issue-still-open",
|
|
171
174
|
tokensUsed,
|
|
172
|
-
inputTokens,
|
|
173
|
-
outputTokens,
|
|
174
|
-
cost,
|
|
175
|
+
inputTokens: result.tokensIn,
|
|
176
|
+
outputTokens: result.tokensOut,
|
|
177
|
+
cost: 0,
|
|
175
178
|
model,
|
|
176
|
-
details: `Issue #${targetIssueNumber} remains open: ${verdict.substring(0, 200)}`,
|
|
177
|
-
narrative: `Reviewed issue #${targetIssueNumber} — still open, not yet resolved.`,
|
|
179
|
+
details: `Issue #${targetIssueNumber} remains open: ${verdictResult.verdict.substring(0, 200)}`,
|
|
180
|
+
narrative: result.narrative || `Reviewed issue #${targetIssueNumber} — still open, not yet resolved.`,
|
|
178
181
|
};
|
|
179
182
|
}
|
|
180
183
|
|
|
@@ -221,18 +224,17 @@ async function findUnreviewedIssues(octokit, repo, limit) {
|
|
|
221
224
|
|
|
222
225
|
/**
|
|
223
226
|
* Review open issues and close those that have been resolved.
|
|
224
|
-
* When no issueNumber is provided, reviews up to 3 issues in batch mode.
|
|
225
227
|
*
|
|
226
228
|
* @param {Object} context - Task context from index.js
|
|
227
229
|
* @returns {Promise<Object>} Result with outcome, tokensUsed, model
|
|
228
230
|
*/
|
|
229
231
|
export async function reviewIssue(context) {
|
|
230
|
-
const { octokit, repo, config, issueNumber, instructions, model } = context;
|
|
232
|
+
const { octokit, repo, config, issueNumber, instructions, model, logFilePath, screenshotFilePath } = context;
|
|
231
233
|
const t = config.tuning || {};
|
|
232
234
|
|
|
233
235
|
// Single issue mode
|
|
234
236
|
if (issueNumber) {
|
|
235
|
-
return reviewSingleIssue({ octokit, repo, config, targetIssueNumber: issueNumber, instructions, model, tuning: t });
|
|
237
|
+
return reviewSingleIssue({ octokit, repo, config, targetIssueNumber: issueNumber, instructions, model, tuning: t, logFilePath, screenshotFilePath });
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
// Batch mode: find up to 3 unreviewed issues
|
|
@@ -245,18 +247,16 @@ export async function reviewIssue(context) {
|
|
|
245
247
|
let totalTokens = 0;
|
|
246
248
|
let totalInputTokens = 0;
|
|
247
249
|
let totalOutputTokens = 0;
|
|
248
|
-
let totalCost = 0;
|
|
249
250
|
|
|
250
251
|
for (const num of issueNumbers) {
|
|
251
252
|
core.info(`Batch reviewing issue #${num} (${results.length + 1}/${issueNumbers.length})`);
|
|
252
253
|
const result = await reviewSingleIssue({
|
|
253
|
-
octokit, repo, config, targetIssueNumber: num, instructions, model, tuning: t,
|
|
254
|
+
octokit, repo, config, targetIssueNumber: num, instructions, model, tuning: t, logFilePath, screenshotFilePath,
|
|
254
255
|
});
|
|
255
256
|
results.push(result);
|
|
256
257
|
totalTokens += result.tokensUsed || 0;
|
|
257
258
|
totalInputTokens += result.inputTokens || 0;
|
|
258
259
|
totalOutputTokens += result.outputTokens || 0;
|
|
259
|
-
totalCost += result.cost || 0;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
const closed = results.filter((r) => r.outcome === "issue-closed").length;
|
|
@@ -267,7 +267,7 @@ export async function reviewIssue(context) {
|
|
|
267
267
|
tokensUsed: totalTokens,
|
|
268
268
|
inputTokens: totalInputTokens,
|
|
269
269
|
outputTokens: totalOutputTokens,
|
|
270
|
-
cost:
|
|
270
|
+
cost: 0,
|
|
271
271
|
model,
|
|
272
272
|
details: `Batch reviewed ${reviewed} issues, closed ${closed}. ${results
|
|
273
273
|
.map((r) => r.details)
|