@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,12 +2,41 @@
|
|
|
2
2
|
// Copyright (C) 2025-2026 Polycode Limited
|
|
3
3
|
// tasks/transform.js — Full mission → features → issues → code pipeline
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// Uses runCopilotSession with lean prompts: the model explores via tools
|
|
6
|
+
// instead of having all context front-loaded into the prompt.
|
|
7
7
|
|
|
8
8
|
import * as core from "@actions/core";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { existsSync, readdirSync, statSync } from "fs";
|
|
10
|
+
import { join, resolve } from "path";
|
|
11
|
+
import { readOptionalFile, formatPathsSection, 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) for the lean prompt.
|
|
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
|
+
const lines = Math.round(stat.size / 40); // rough estimate
|
|
30
|
+
return `${f} (~${lines} lines, ${stat.size} bytes)`;
|
|
31
|
+
} catch {
|
|
32
|
+
return String(f);
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
.slice(0, 30); // cap listing at 30 files
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
11
40
|
|
|
12
41
|
/**
|
|
13
42
|
* Run the full transformation pipeline from mission to code.
|
|
@@ -16,7 +45,7 @@ import { runCopilotTask, readOptionalFile, scanDirectory, formatPathsSection, fi
|
|
|
16
45
|
* @returns {Promise<Object>} Result with outcome, tokensUsed, model
|
|
17
46
|
*/
|
|
18
47
|
export async function transform(context) {
|
|
19
|
-
const { config, instructions, writablePaths, testCommand, model, octokit, repo, issueNumber } = context;
|
|
48
|
+
const { config, instructions, writablePaths, testCommand, model, octokit, repo, issueNumber, logFilePath, screenshotFilePath } = context;
|
|
20
49
|
const t = config.tuning || {};
|
|
21
50
|
|
|
22
51
|
// Read mission (required)
|
|
@@ -32,39 +61,21 @@ export async function transform(context) {
|
|
|
32
61
|
return { outcome: "nop", details: "Mission already complete (MISSION_COMPLETE.md signal)" };
|
|
33
62
|
}
|
|
34
63
|
|
|
35
|
-
const features = scanDirectory(config.paths.features.path, ".md", { fileLimit: t.featuresScan || 10 });
|
|
36
|
-
const sourceFiles = scanDirectory(config.paths.source.path, [".js", ".ts"], {
|
|
37
|
-
fileLimit: t.sourceScan || 10,
|
|
38
|
-
contentLimit: t.sourceContent || 5000,
|
|
39
|
-
recursive: true,
|
|
40
|
-
sortByMtime: true,
|
|
41
|
-
clean: true,
|
|
42
|
-
outline: true,
|
|
43
|
-
});
|
|
44
|
-
const webFiles = scanDirectory(config.paths.web?.path || "src/web/", [".html", ".css", ".js"], {
|
|
45
|
-
fileLimit: t.sourceScan || 10,
|
|
46
|
-
contentLimit: t.sourceContent || 5000,
|
|
47
|
-
recursive: true,
|
|
48
|
-
sortByMtime: true,
|
|
49
|
-
clean: true,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const { data: rawIssues } = await octokit.rest.issues.listForRepo({
|
|
53
|
-
...repo,
|
|
54
|
-
state: "open",
|
|
55
|
-
per_page: t.issuesScan || 20,
|
|
56
|
-
});
|
|
57
|
-
const openIssues = filterIssues(rawIssues, { staleDays: t.staleDays || 30 });
|
|
58
|
-
|
|
59
64
|
// Fetch target issue if specified
|
|
60
|
-
let
|
|
65
|
+
let targetIssueSection = "";
|
|
61
66
|
if (issueNumber) {
|
|
62
67
|
try {
|
|
63
68
|
const { data: issue } = await octokit.rest.issues.get({
|
|
64
69
|
...repo,
|
|
65
70
|
issue_number: Number(issueNumber),
|
|
66
71
|
});
|
|
67
|
-
|
|
72
|
+
targetIssueSection = [
|
|
73
|
+
`## Target Issue #${issue.number}: ${issue.title}`,
|
|
74
|
+
issue.body || "(no description)",
|
|
75
|
+
`Labels: ${issue.labels.map((l) => l.name).join(", ") || "none"}`,
|
|
76
|
+
"",
|
|
77
|
+
"**Focus your transformation on resolving this specific issue.**",
|
|
78
|
+
].join("\n");
|
|
68
79
|
} catch (err) {
|
|
69
80
|
core.warning(`Could not fetch target issue #${issueNumber}: ${err.message}`);
|
|
70
81
|
}
|
|
@@ -72,267 +83,104 @@ export async function transform(context) {
|
|
|
72
83
|
|
|
73
84
|
const agentInstructions =
|
|
74
85
|
instructions || "Transform the repository toward its mission by identifying the next best action.";
|
|
75
|
-
const readOnlyPaths = config.readOnlyPaths;
|
|
76
86
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
writablePaths,
|
|
83
|
-
readOnlyPaths,
|
|
84
|
-
testCommand,
|
|
85
|
-
model,
|
|
86
|
-
mission,
|
|
87
|
-
features,
|
|
88
|
-
sourceFiles,
|
|
89
|
-
webFiles,
|
|
90
|
-
openIssues,
|
|
91
|
-
tuning: t,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
87
|
+
// ── Build lean prompt (structure + mission, not file contents) ──────
|
|
88
|
+
const sourceFiles = buildFileListing(config.paths.source.path, [".js", ".ts"]);
|
|
89
|
+
const testFiles = buildFileListing(config.paths.tests.path, [".js", ".ts"]);
|
|
90
|
+
const webFiles = buildFileListing(config.paths.web?.path || "src/web/", [".html", ".css", ".js"]);
|
|
91
|
+
const featureFiles = buildFileListing(config.paths.features.path, [".md"]);
|
|
94
92
|
|
|
95
93
|
const prompt = [
|
|
96
94
|
"## Instructions",
|
|
97
95
|
agentInstructions,
|
|
98
96
|
"",
|
|
99
|
-
...(
|
|
100
|
-
? [
|
|
101
|
-
`## Target Issue #${targetIssue.number}: ${targetIssue.title}`,
|
|
102
|
-
targetIssue.body || "(no description)",
|
|
103
|
-
`Labels: ${targetIssue.labels.map((l) => l.name).join(", ") || "none"}`,
|
|
104
|
-
"",
|
|
105
|
-
"**Focus your transformation on resolving this specific issue.**",
|
|
106
|
-
"",
|
|
107
|
-
]
|
|
108
|
-
: []),
|
|
97
|
+
...(targetIssueSection ? [targetIssueSection, ""] : []),
|
|
109
98
|
"## Mission",
|
|
110
99
|
mission,
|
|
111
100
|
"",
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
""
|
|
115
|
-
|
|
116
|
-
...
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"The website in `src/web/` uses the JS library.",
|
|
122
|
-
"When transforming source code, also update the website to use the library's new/changed features.",
|
|
123
|
-
"`src/web/lib.js` re-exports from `../lib/main.js` so the page imports the real library directly. Keep `main.js` browser-safe (guard Node APIs behind `typeof process` checks). NEVER duplicate library functions inline in the web page.",
|
|
124
|
-
...webFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
125
|
-
"",
|
|
126
|
-
]
|
|
127
|
-
: []),
|
|
128
|
-
`## Open Issues (${openIssues.length})`,
|
|
129
|
-
...openIssues.slice(0, t.issuesScan || 20).map((i) => summariseIssue(i, t.issueBodyLimit || 500)),
|
|
130
|
-
"",
|
|
131
|
-
"## Output Artifacts",
|
|
132
|
-
"If your changes produce output artifacts (plots, visualizations, data files, usage examples),",
|
|
133
|
-
`save them to the \`${config.paths.examples?.path || "examples/"}\` directory.`,
|
|
134
|
-
"This directory is for demonstrating what the code can do.",
|
|
101
|
+
"## Repository Structure",
|
|
102
|
+
`Source files (${sourceFiles.length}): ${sourceFiles.join(", ") || "none"}`,
|
|
103
|
+
`Test files (${testFiles.length}): ${testFiles.join(", ") || "none"}`,
|
|
104
|
+
`Features (${featureFiles.length}): ${featureFiles.join(", ") || "none"}`,
|
|
105
|
+
...(webFiles.length > 0 ? [
|
|
106
|
+
`Website files (${webFiles.length}): ${webFiles.join(", ")}`,
|
|
107
|
+
"The website in `src/web/` uses the JS library. `src/web/lib.js` re-exports from `../lib/main.js`.",
|
|
108
|
+
"When transforming source code, also update the website to use the library's new/changed features.",
|
|
109
|
+
] : []),
|
|
135
110
|
"",
|
|
136
111
|
"## Your Task",
|
|
137
|
-
"Analyze the mission
|
|
112
|
+
"Analyze the mission and open issues (use list_issues tool).",
|
|
113
|
+
"Read the source files you need (use read_file tool).",
|
|
138
114
|
"Determine the single most impactful next step to transform this repository.",
|
|
139
|
-
"Then implement that step.",
|
|
115
|
+
"Then implement that step, writing files and running run_tests to verify.",
|
|
140
116
|
"",
|
|
141
117
|
"## When NOT to make changes",
|
|
142
118
|
"If the existing code already satisfies all requirements in MISSION.md and all open issues have been addressed:",
|
|
143
119
|
"- Do NOT make cosmetic changes (reformatting, renaming, reordering)",
|
|
144
120
|
"- Do NOT add features beyond what MISSION.md specifies",
|
|
145
|
-
"- Do NOT rewrite working code for stylistic preferences",
|
|
146
121
|
"- Instead, report that the mission is satisfied and make no file changes",
|
|
147
122
|
"",
|
|
148
|
-
formatPathsSection(writablePaths, readOnlyPaths, config),
|
|
123
|
+
formatPathsSection(writablePaths, config.readOnlyPaths, config),
|
|
149
124
|
"",
|
|
150
125
|
"## Constraints",
|
|
151
|
-
`- Run \`${testCommand}\` to validate your changes`,
|
|
126
|
+
`- Run \`${testCommand}\` via run_tests to validate your changes`,
|
|
127
|
+
"- Use list_issues to see open issues, get_issue for full details",
|
|
128
|
+
"- Use read_file to read source files you need (don't guess at contents)",
|
|
152
129
|
].join("\n");
|
|
153
130
|
|
|
154
|
-
core.info(`Transform prompt length: ${prompt.length} chars`);
|
|
155
|
-
|
|
156
|
-
const {
|
|
157
|
-
content: resultContent,
|
|
158
|
-
tokensUsed,
|
|
159
|
-
inputTokens,
|
|
160
|
-
outputTokens,
|
|
161
|
-
cost,
|
|
162
|
-
} = await runCopilotTask({
|
|
163
|
-
model,
|
|
164
|
-
systemMessage:
|
|
165
|
-
"You are an autonomous code transformation agent. Your goal is to advance the repository toward its mission by making the most impactful change possible in a single step." + NARRATIVE_INSTRUCTION,
|
|
166
|
-
prompt,
|
|
167
|
-
writablePaths,
|
|
168
|
-
tuning: t,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
core.info(`Transformation step completed (${tokensUsed} tokens)`);
|
|
131
|
+
core.info(`Transform lean prompt length: ${prompt.length} chars`);
|
|
172
132
|
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
core.info("Transform indicates mission may be complete — supervisor will verify on next cycle");
|
|
133
|
+
// ── Build attachments (mission + log + screenshot) ─────────────────
|
|
134
|
+
const attachments = [];
|
|
135
|
+
const missionPath = resolve(config.paths.mission.path);
|
|
136
|
+
if (existsSync(missionPath)) {
|
|
137
|
+
attachments.push({ type: "file", path: missionPath });
|
|
179
138
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
outputTokens,
|
|
193
|
-
cost,
|
|
194
|
-
model,
|
|
195
|
-
details: resultContent.substring(0, 500),
|
|
196
|
-
narrative: extractNarrative(resultContent, "Transformation step completed."),
|
|
197
|
-
promptBudget,
|
|
198
|
-
contextNotes: `Transformed with ${sourceFiles.length} source files (mtime-sorted, cleaned), ${features.length} features, ${openIssues.length} issues (${rawIssues.length - openIssues.length} stale filtered).`,
|
|
139
|
+
if (logFilePath) attachments.push({ type: "file", path: logFilePath });
|
|
140
|
+
if (screenshotFilePath) attachments.push({ type: "file", path: screenshotFilePath });
|
|
141
|
+
|
|
142
|
+
// ── System prompt ──────────────────────────────────────────────────
|
|
143
|
+
const systemPrompt =
|
|
144
|
+
"You are an autonomous code transformation agent. Your goal is to advance the repository toward its mission by making the most impactful change possible in a single step." + NARRATIVE_INSTRUCTION;
|
|
145
|
+
|
|
146
|
+
// ── Create custom tools (GitHub API + git) ─────────────────────────
|
|
147
|
+
const createTools = (defineTool, _wp, logger) => {
|
|
148
|
+
const ghTools = createGitHubTools(octokit, repo, defineTool, logger);
|
|
149
|
+
const gitTools = createGitTools(defineTool, logger);
|
|
150
|
+
return [...ghTools, ...gitTools];
|
|
199
151
|
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* TDD-mode transformation: Phase 1 creates a failing test, Phase 2 writes implementation.
|
|
204
|
-
*/
|
|
205
|
-
async function transformTdd({
|
|
206
|
-
config: _config,
|
|
207
|
-
instructions,
|
|
208
|
-
writablePaths,
|
|
209
|
-
readOnlyPaths,
|
|
210
|
-
testCommand,
|
|
211
|
-
model,
|
|
212
|
-
mission,
|
|
213
|
-
features,
|
|
214
|
-
sourceFiles,
|
|
215
|
-
webFiles,
|
|
216
|
-
openIssues,
|
|
217
|
-
tuning: t,
|
|
218
|
-
}) {
|
|
219
|
-
let totalTokens = 0;
|
|
220
|
-
|
|
221
|
-
// Phase 1: Create a failing test
|
|
222
|
-
core.info("TDD Phase 1: Creating failing test");
|
|
223
|
-
|
|
224
|
-
const testPrompt = [
|
|
225
|
-
"## Instructions",
|
|
226
|
-
instructions,
|
|
227
|
-
"",
|
|
228
|
-
"## Mode: TDD Phase 1 — Write Failing Test",
|
|
229
|
-
"You are in TDD mode. In this phase, you must ONLY write a test.",
|
|
230
|
-
"The test should capture the next feature requirement based on the mission and current state.",
|
|
231
|
-
"The test MUST fail against the current codebase (it tests something not yet implemented).",
|
|
232
|
-
"Do NOT write any implementation code in this phase.",
|
|
233
|
-
"",
|
|
234
|
-
"## Mission",
|
|
235
|
-
mission,
|
|
236
|
-
"",
|
|
237
|
-
`## Current Features (${features.length})`,
|
|
238
|
-
...features.map((f) => `### ${f.name}\n${extractFeatureSummary(f.content, f.name)}`),
|
|
239
|
-
"",
|
|
240
|
-
`## Current Source Files (${sourceFiles.length})`,
|
|
241
|
-
...sourceFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
242
|
-
"",
|
|
243
|
-
...(webFiles.length > 0
|
|
244
|
-
? [
|
|
245
|
-
`## Website Files (${webFiles.length})`,
|
|
246
|
-
"The website in `src/web/` uses the JS library.",
|
|
247
|
-
...webFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
248
|
-
"",
|
|
249
|
-
]
|
|
250
|
-
: []),
|
|
251
|
-
`## Open Issues (${openIssues.length})`,
|
|
252
|
-
...openIssues.slice(0, t.issuesScan || 20).map((i) => summariseIssue(i, t.issueBodyLimit || 500)),
|
|
253
|
-
"",
|
|
254
|
-
formatPathsSection(writablePaths, readOnlyPaths, _config),
|
|
255
|
-
"",
|
|
256
|
-
"## Constraints",
|
|
257
|
-
"- Write ONLY test code in this phase",
|
|
258
|
-
"- The test must fail when run (it tests unimplemented functionality)",
|
|
259
|
-
`- Run \`${testCommand}\` to confirm the test fails`,
|
|
260
|
-
].join("\n");
|
|
261
152
|
|
|
262
|
-
|
|
153
|
+
// ── Run hybrid session ─────────────────────────────────────────────
|
|
154
|
+
const result = await runCopilotSession({
|
|
155
|
+
workspacePath: process.cwd(),
|
|
263
156
|
model,
|
|
264
|
-
systemMessage:
|
|
265
|
-
"You are a TDD agent. In this phase, write ONLY a failing test that captures the next feature requirement. Do not write implementation code.",
|
|
266
|
-
prompt: testPrompt,
|
|
267
|
-
writablePaths,
|
|
268
157
|
tuning: t,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const testResult = phase1.content;
|
|
272
|
-
|
|
273
|
-
core.info(`TDD Phase 1 completed (${totalTokens} tokens): test created`);
|
|
274
|
-
|
|
275
|
-
// Phase 2: Write implementation to make the test pass
|
|
276
|
-
core.info("TDD Phase 2: Writing implementation");
|
|
277
|
-
|
|
278
|
-
const implPrompt = [
|
|
279
|
-
"## Instructions",
|
|
280
|
-
instructions,
|
|
281
|
-
"",
|
|
282
|
-
"## Mode: TDD Phase 2 — Write Implementation",
|
|
283
|
-
"A failing test has been written in Phase 1. Your job is to write the MINIMUM implementation",
|
|
284
|
-
"code needed to make the test pass. Do not modify the test.",
|
|
285
|
-
"",
|
|
286
|
-
"## What was done in Phase 1",
|
|
287
|
-
testResult.substring(0, 1000),
|
|
288
|
-
"",
|
|
289
|
-
"## Mission",
|
|
290
|
-
mission,
|
|
291
|
-
"",
|
|
292
|
-
`## Current Source Files (${sourceFiles.length})`,
|
|
293
|
-
...sourceFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
294
|
-
"",
|
|
295
|
-
...(webFiles.length > 0
|
|
296
|
-
? [
|
|
297
|
-
`## Website Files (${webFiles.length})`,
|
|
298
|
-
"The website in `src/web/` uses the JS library. Update it to reflect any new features.",
|
|
299
|
-
...webFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
300
|
-
"",
|
|
301
|
-
]
|
|
302
|
-
: []),
|
|
303
|
-
formatPathsSection(writablePaths, readOnlyPaths, _config),
|
|
304
|
-
"",
|
|
305
|
-
"## Output Artifacts",
|
|
306
|
-
"If your changes produce output artifacts (plots, visualizations, data files, usage examples),",
|
|
307
|
-
`save them to the \`${_config.paths.examples?.path || "examples/"}\` directory.`,
|
|
308
|
-
"This directory is for demonstrating what the code can do.",
|
|
309
|
-
"",
|
|
310
|
-
"## Constraints",
|
|
311
|
-
"- Write implementation code to make the failing test pass",
|
|
312
|
-
"- Do NOT modify the test file created in Phase 1",
|
|
313
|
-
`- Run \`${testCommand}\` to confirm all tests pass`,
|
|
314
|
-
].join("\n");
|
|
315
|
-
|
|
316
|
-
const phase2 = await runCopilotTask({
|
|
317
|
-
model,
|
|
318
|
-
systemMessage:
|
|
319
|
-
"You are a TDD agent. A failing test was written in Phase 1. Write the minimum implementation to make it pass. Do not modify the test." + NARRATIVE_INSTRUCTION,
|
|
320
|
-
prompt: implPrompt,
|
|
158
|
+
agentPrompt: systemPrompt,
|
|
159
|
+
userPrompt: prompt,
|
|
321
160
|
writablePaths,
|
|
322
|
-
|
|
161
|
+
createTools,
|
|
162
|
+
attachments,
|
|
163
|
+
excludedTools: ["dispatch_workflow", "close_issue", "label_issue", "post_discussion_comment"],
|
|
164
|
+
logger: { info: core.info, warning: core.warning, error: core.error, debug: core.debug },
|
|
323
165
|
});
|
|
324
|
-
totalTokens += phase2.tokensUsed;
|
|
325
166
|
|
|
326
|
-
core.info(`
|
|
167
|
+
core.info(`Transformation step completed (${result.tokensIn + result.tokensOut} tokens)`);
|
|
168
|
+
|
|
169
|
+
// Detect mission-complete hint
|
|
170
|
+
const lowerResult = (result.agentMessage || "").toLowerCase();
|
|
171
|
+
if (lowerResult.includes("mission is satisfied") || lowerResult.includes("mission is complete") || lowerResult.includes("no changes needed")) {
|
|
172
|
+
core.info("Transform indicates mission may be complete — supervisor will verify on next cycle");
|
|
173
|
+
}
|
|
327
174
|
|
|
328
175
|
return {
|
|
329
|
-
outcome: "transformed
|
|
330
|
-
tokensUsed:
|
|
331
|
-
inputTokens:
|
|
332
|
-
outputTokens:
|
|
333
|
-
cost:
|
|
176
|
+
outcome: result.testsPassed ? "transformed" : "transformed",
|
|
177
|
+
tokensUsed: result.tokensIn + result.tokensOut,
|
|
178
|
+
inputTokens: result.tokensIn,
|
|
179
|
+
outputTokens: result.tokensOut,
|
|
180
|
+
cost: 0,
|
|
334
181
|
model,
|
|
335
|
-
details:
|
|
336
|
-
narrative: extractNarrative(
|
|
182
|
+
details: (result.agentMessage || "").substring(0, 500),
|
|
183
|
+
narrative: result.narrative || extractNarrative(result.agentMessage, "Transformation step completed."),
|
|
184
|
+
contextNotes: `Lean prompt transform: ${result.toolCalls} tool calls, ${result.testRuns} test runs, ${result.filesWritten} files written in ${result.sessionTime}s.`,
|
|
337
185
|
};
|
|
338
186
|
}
|
package/src/copilot/agents.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
// Copyright (C) 2025-2026 Polycode Limited
|
|
3
|
-
// src/copilot/agents.js — Load agent prompt files from
|
|
3
|
+
// src/copilot/agents.js — Load agent prompt files from .github/agents/
|
|
4
4
|
|
|
5
5
|
import { readFileSync, readdirSync } from "fs";
|
|
6
6
|
import { resolve, dirname } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
const agentsDir = resolve(__dirname, "..", "agents");
|
|
10
|
+
const agentsDir = resolve(__dirname, "..", "..", ".github", "agents");
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Load an agent prompt file by name.
|
package/src/copilot/config.js
CHANGED
|
@@ -66,14 +66,8 @@ const FALLBACK_TUNING = {
|
|
|
66
66
|
reasoningEffort: "medium",
|
|
67
67
|
infiniteSessions: true,
|
|
68
68
|
transformationBudget: 32,
|
|
69
|
-
featuresScan: 10,
|
|
70
|
-
sourceScan: 10,
|
|
71
|
-
sourceContent: 5000,
|
|
72
|
-
testContent: 3000,
|
|
73
69
|
issuesScan: 20,
|
|
74
|
-
issueBodyLimit: 500,
|
|
75
70
|
staleDays: 30,
|
|
76
|
-
documentSummary: 2000,
|
|
77
71
|
discussionComments: 10,
|
|
78
72
|
};
|
|
79
73
|
|
|
@@ -95,14 +89,8 @@ function parseTuningProfile(profileSection) {
|
|
|
95
89
|
reasoningEffort: profileSection["reasoning-effort"] || "medium",
|
|
96
90
|
infiniteSessions: profileSection["infinite-sessions"] ?? true,
|
|
97
91
|
transformationBudget: profileSection["transformation-budget"] || 32,
|
|
98
|
-
featuresScan: profileSection["max-feature-files"] || 10,
|
|
99
|
-
sourceScan: profileSection["max-source-files"] || 10,
|
|
100
|
-
sourceContent: profileSection["max-source-chars"] || 5000,
|
|
101
|
-
testContent: profileSection["max-test-chars"] || 3000,
|
|
102
92
|
issuesScan: profileSection["max-issues"] || 20,
|
|
103
|
-
issueBodyLimit: profileSection["issue-body-limit"] || 500,
|
|
104
93
|
staleDays: profileSection["stale-days"] || 30,
|
|
105
|
-
documentSummary: profileSection["max-summary-chars"] || 2000,
|
|
106
94
|
discussionComments: profileSection["max-discussion-comments"] || 10,
|
|
107
95
|
};
|
|
108
96
|
}
|
|
@@ -158,14 +146,8 @@ function resolveTuning(tuningSection, profilesSection) {
|
|
|
158
146
|
}
|
|
159
147
|
const numericOverrides = {
|
|
160
148
|
"transformation-budget": "transformationBudget",
|
|
161
|
-
"max-feature-files": "featuresScan",
|
|
162
|
-
"max-source-files": "sourceScan",
|
|
163
|
-
"max-source-chars": "sourceContent",
|
|
164
|
-
"max-test-chars": "testContent",
|
|
165
149
|
"max-issues": "issuesScan",
|
|
166
|
-
"issue-body-limit": "issueBodyLimit",
|
|
167
150
|
"stale-days": "staleDays",
|
|
168
|
-
"max-summary-chars": "documentSummary",
|
|
169
151
|
"max-discussion-comments": "discussionComments",
|
|
170
152
|
};
|
|
171
153
|
for (const [tomlKey, jsKey] of Object.entries(numericOverrides)) {
|
|
@@ -279,6 +261,8 @@ export function loadConfig(configPath) {
|
|
|
279
261
|
seeding: toml.seeding || {},
|
|
280
262
|
intentionBot: {
|
|
281
263
|
intentionFilepath: bot["log-file"] || "intentïon.md",
|
|
264
|
+
logBranch: bot["log-branch"] || "agentic-lib-logs",
|
|
265
|
+
screenshotFile: bot["screenshot-file"] || "SCREENSHOT_INDEX.png",
|
|
282
266
|
},
|
|
283
267
|
init: toml.init || null,
|
|
284
268
|
tdd: toml.tdd === true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
// Copyright (C) 2025-2026 Polycode Limited
|
|
3
|
-
// src/copilot/
|
|
3
|
+
// src/copilot/copilot-session.js — Single-session Copilot SDK runner
|
|
4
4
|
//
|
|
5
5
|
// Replaces the old multi-session runIterationLoop with a single persistent
|
|
6
6
|
// Copilot SDK session that drives its own tool loop. Hooks provide
|
|
@@ -63,10 +63,13 @@ function formatToolArgs(toolName, args) {
|
|
|
63
63
|
* @param {string[]} [options.writablePaths] - Writable paths for tool safety (default: workspace)
|
|
64
64
|
* @param {number} [options.maxRetries=2] - Max retries on rate-limit errors
|
|
65
65
|
* @param {number} [options.maxToolCalls] - Max tool calls before graceful stop (undefined = unlimited)
|
|
66
|
+
* @param {Function} [options.createTools] - (defineTool, writablePaths, logger) => Tool[] — custom tool factory
|
|
67
|
+
* @param {string[]} [options.excludedTools] - Tool names to exclude from the session
|
|
68
|
+
* @param {Array} [options.attachments] - File/directory attachments for sendAndWait
|
|
66
69
|
* @param {Object} [options.logger]
|
|
67
70
|
* @returns {Promise<HybridResult>}
|
|
68
71
|
*/
|
|
69
|
-
export async function
|
|
72
|
+
export async function runCopilotSession({
|
|
70
73
|
workspacePath,
|
|
71
74
|
model = "gpt-5-mini",
|
|
72
75
|
githubToken,
|
|
@@ -77,6 +80,9 @@ export async function runHybridSession({
|
|
|
77
80
|
writablePaths,
|
|
78
81
|
maxRetries = 2,
|
|
79
82
|
maxToolCalls,
|
|
83
|
+
createTools,
|
|
84
|
+
excludedTools,
|
|
85
|
+
attachments,
|
|
80
86
|
logger = defaultLogger,
|
|
81
87
|
}) {
|
|
82
88
|
const { CopilotClient, approveAll, defineTool } = await getSDK();
|
|
@@ -132,9 +138,10 @@ export async function runHybridSession({
|
|
|
132
138
|
});
|
|
133
139
|
|
|
134
140
|
// ── Build full tool set ─────────────────────────────────────────────
|
|
135
|
-
// 4 standard tools (read_file, write_file, list_files, run_command) + run_tests
|
|
141
|
+
// 4 standard tools (read_file, write_file, list_files, run_command) + run_tests + custom
|
|
136
142
|
const agentTools = createAgentTools(effectiveWritablePaths, logger, defineTool);
|
|
137
|
-
const
|
|
143
|
+
const customTools = createTools ? createTools(defineTool, effectiveWritablePaths, logger) : [];
|
|
144
|
+
const allTools = [...agentTools, runTestsTool, ...customTools];
|
|
138
145
|
|
|
139
146
|
// ── Build system prompt with narrative instruction ─────────────────
|
|
140
147
|
const basePrompt = agentPrompt || [
|
|
@@ -155,16 +162,17 @@ export async function runHybridSession({
|
|
|
155
162
|
|
|
156
163
|
const sessionConfig = {
|
|
157
164
|
model,
|
|
158
|
-
systemMessage: { mode: "
|
|
165
|
+
systemMessage: { mode: "append", content: systemPrompt },
|
|
159
166
|
tools: allTools,
|
|
160
167
|
onPermissionRequest: approveAll,
|
|
161
168
|
workingDirectory: wsPath,
|
|
169
|
+
...(excludedTools && excludedTools.length > 0 ? { excludedTools } : {}),
|
|
162
170
|
hooks: {
|
|
163
171
|
onPreToolUse: (input) => {
|
|
164
172
|
// Enforce tool-call budget
|
|
165
173
|
if (maxToolCalls && metrics.toolCalls.length >= maxToolCalls) {
|
|
166
174
|
logger.warning(` [tool] Budget reached (${maxToolCalls} calls) — denying ${input.toolName}`);
|
|
167
|
-
return {
|
|
175
|
+
return { permissionDecision: "deny", permissionDecisionReason: `Tool call budget exhausted (${maxToolCalls} max). Wrap up your work.` };
|
|
168
176
|
}
|
|
169
177
|
const n = metrics.toolCalls.length + 1;
|
|
170
178
|
const elapsed = ((Date.now() - metrics.startTime) / 1000).toFixed(0);
|
|
@@ -173,11 +181,27 @@ export async function runHybridSession({
|
|
|
173
181
|
logger.info(` [tool #${n} +${elapsed}s] ${input.toolName}${detail}`);
|
|
174
182
|
},
|
|
175
183
|
onPostToolUse: (input) => {
|
|
184
|
+
const hookOutput = {};
|
|
185
|
+
|
|
176
186
|
if (/write|edit|create/i.test(input.toolName)) {
|
|
177
187
|
const path = input.toolArgs?.file_path || input.toolArgs?.path || "unknown";
|
|
178
188
|
metrics.filesWritten.add(path);
|
|
179
189
|
logger.info(` → wrote ${path}`);
|
|
180
190
|
}
|
|
191
|
+
|
|
192
|
+
// Truncate large read_file results to prevent context overflow
|
|
193
|
+
if (input.toolName === "read_file" || input.toolName === "view") {
|
|
194
|
+
const resultText = input.toolResult?.textResultForLlm || "";
|
|
195
|
+
const MAX_READ_CHARS = 20000;
|
|
196
|
+
if (resultText.length > MAX_READ_CHARS) {
|
|
197
|
+
hookOutput.modifiedResult = {
|
|
198
|
+
...input.toolResult,
|
|
199
|
+
textResultForLlm: resultText.substring(0, MAX_READ_CHARS) + `\n... (truncated from ${resultText.length} chars)`,
|
|
200
|
+
};
|
|
201
|
+
logger.info(` → truncated read_file output: ${resultText.length} → ${MAX_READ_CHARS} chars`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
181
205
|
if (input.toolName === "run_tests" || input.toolName === "run_command" || input.toolName === "bash") {
|
|
182
206
|
const result = input.toolResult?.textResultForLlm || input.toolResult || "";
|
|
183
207
|
const resultStr = typeof result === "string" ? result : JSON.stringify(result);
|
|
@@ -188,8 +212,12 @@ export async function runHybridSession({
|
|
|
188
212
|
} else if (failed) {
|
|
189
213
|
const failMatch = resultStr.match(/(\d+)\s*(failed|fail)/i);
|
|
190
214
|
logger.info(` → tests FAILED${failMatch ? ` (${failMatch[1]} failures)` : ""}`);
|
|
215
|
+
// Inject guidance when tests fail
|
|
216
|
+
hookOutput.additionalContext = "Focus on the failing tests. Read the test file to understand expectations before changing source code.";
|
|
191
217
|
}
|
|
192
218
|
}
|
|
219
|
+
|
|
220
|
+
return Object.keys(hookOutput).length > 0 ? hookOutput : undefined;
|
|
193
221
|
},
|
|
194
222
|
onErrorOccurred: (input) => {
|
|
195
223
|
metrics.errors.push({ error: input.error, context: input.errorContext, time: Date.now() });
|
|
@@ -281,9 +309,13 @@ export async function runHybridSession({
|
|
|
281
309
|
let endReason = "complete";
|
|
282
310
|
|
|
283
311
|
// Send with rate-limit retry
|
|
312
|
+
const sendOptions = { prompt };
|
|
313
|
+
if (attachments && attachments.length > 0) {
|
|
314
|
+
sendOptions.attachments = attachments;
|
|
315
|
+
}
|
|
284
316
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
285
317
|
try {
|
|
286
|
-
response = await session.sendAndWait(
|
|
318
|
+
response = await session.sendAndWait(sendOptions, timeoutMs);
|
|
287
319
|
break;
|
|
288
320
|
} catch (err) {
|
|
289
321
|
if (isRateLimitError(err) && attempt < maxRetries) {
|