@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.
Files changed (45) hide show
  1. package/{src → .github}/agents/agent-apply-fix.md +10 -0
  2. package/{src → .github}/agents/agent-director.md +10 -0
  3. package/{src → .github}/agents/agent-discovery.md +8 -0
  4. package/{src → .github}/agents/agent-discussion-bot.md +9 -0
  5. package/{src → .github}/agents/agent-issue-resolution.md +12 -0
  6. package/{src → .github}/agents/agent-iterate.md +8 -0
  7. package/{src → .github}/agents/agent-maintain-features.md +8 -0
  8. package/{src → .github}/agents/agent-maintain-library.md +7 -0
  9. package/{src → .github}/agents/agent-review-issue.md +8 -0
  10. package/{src → .github}/agents/agent-supervisor.md +9 -0
  11. package/.github/workflows/agentic-lib-test.yml +4 -2
  12. package/.github/workflows/agentic-lib-workflow.yml +70 -26
  13. package/README.md +5 -7
  14. package/agentic-lib.toml +16 -38
  15. package/bin/agentic-lib.js +49 -60
  16. package/package.json +3 -4
  17. package/src/actions/agentic-step/action.yml +1 -1
  18. package/src/actions/agentic-step/copilot.js +0 -5
  19. package/src/actions/agentic-step/index.js +8 -1
  20. package/src/actions/agentic-step/logging.js +14 -2
  21. package/src/actions/agentic-step/tasks/direct.js +86 -65
  22. package/src/actions/agentic-step/tasks/discussions.js +198 -264
  23. package/src/actions/agentic-step/tasks/enhance-issue.js +84 -33
  24. package/src/actions/agentic-step/tasks/fix-code.js +111 -57
  25. package/src/actions/agentic-step/tasks/maintain-features.js +69 -52
  26. package/src/actions/agentic-step/tasks/maintain-library.js +57 -19
  27. package/src/actions/agentic-step/tasks/resolve-issue.js +43 -18
  28. package/src/actions/agentic-step/tasks/review-issue.js +117 -117
  29. package/src/actions/agentic-step/tasks/supervise.js +140 -151
  30. package/src/actions/agentic-step/tasks/transform.js +106 -258
  31. package/src/copilot/agents.js +2 -2
  32. package/src/copilot/config.js +2 -18
  33. package/src/copilot/{hybrid-session.js → copilot-session.js} +39 -7
  34. package/src/copilot/github-tools.js +514 -0
  35. package/src/copilot/guards.js +1 -1
  36. package/src/copilot/session.js +0 -141
  37. package/src/copilot/tools.js +4 -0
  38. package/src/iterate.js +1 -1
  39. package/src/scripts/push-to-logs.sh +1 -1
  40. package/src/seeds/zero-SCREENSHOT_INDEX.png +0 -0
  41. package/src/seeds/zero-package.json +1 -1
  42. package/src/agents/agentic-lib.yml +0 -66
  43. package/src/copilot/context.js +0 -457
  44. package/src/mcp/server.js +0 -830
  45. /package/{src → .github}/agents/agent-ready-issue.md +0 -0
package/src/iterate.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  // Copyright (C) 2025-2026 Polycode Limited
3
- // src/iterate.js — Shared iteration loop for CLI and MCP server
3
+ // src/iterate.js — Shared iteration loop for CLI
4
4
  //
5
5
  // Runs N cycles of maintain → transform → fix, tracking transformation cost
6
6
  // against a budget. Stops early on consecutive test passes, no-progress, or
@@ -11,7 +11,7 @@
11
11
 
12
12
  set -euo pipefail
13
13
 
14
- BRANCH="agentic-lib-logs"
14
+ BRANCH="${LOG_BRANCH:-agentic-lib-logs}"
15
15
  MAX_RETRIES=3
16
16
 
17
17
  # Collect files that actually exist in the workspace
@@ -17,7 +17,7 @@
17
17
  "author": "",
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
- "@xn-intenton-z2a/agentic-lib": "^7.4.8"
20
+ "@xn-intenton-z2a/agentic-lib": "^7.4.9"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@playwright/test": "^1.58.0",
@@ -1,66 +0,0 @@
1
- # Mapping for from symbolic keys to filepaths for access by agentic-lib workflows with limits and access permissions
2
- paths:
3
- # Filepaths for elaborator workflows
4
- missionFilepath:
5
- path: "MISSION.md"
6
- librarySourcesFilepath:
7
- path: "SOURCES.md"
8
- permissions: ["write"]
9
- limit: 32
10
- libraryDocumentsPath:
11
- path: "library/"
12
- permissions: ["write"]
13
- limit: 32
14
- featuresPath:
15
- path: "features/"
16
- permissions: ["write"]
17
- limit: 4
18
-
19
- # Filepaths for engineer workflows
20
- contributingFilepath:
21
- path: "CONTRIBUTING.md"
22
- targetTestsPath:
23
- path: "tests/unit/"
24
- permissions: ["write"]
25
- targetSourcePath:
26
- path: "src/lib/"
27
- permissions: ["write"]
28
- targetWebPath:
29
- path: "src/web/"
30
- permissions: ["write"]
31
- dependenciesFilepath:
32
- path: "package.json"
33
- permissions: ["write"]
34
- documentationPath:
35
- path: "docs/"
36
- permissions: ["write"]
37
-
38
- # Filepaths for maintainer workflows
39
- formattingFilepath:
40
- path: ".prettierrc"
41
- lintingFilepath:
42
- path: "eslint.config.js"
43
- readmeFilepath:
44
- path: "README.md"
45
- permissions: ["write"]
46
-
47
- # Execution commands
48
- testScript: "npm ci && npm test"
49
-
50
- # How many issues should be available to be picked up?
51
- featureDevelopmentIssuesWipLimit: 2
52
- maintenanceIssuesWipLimit: 1
53
-
54
- # How many attempts should be made to work on an issue?
55
- attemptsPerBranch: 3
56
- attemptsPerIssue: 2
57
-
58
- # Repository seeding
59
- seeding:
60
- missionFilepath: "src/seeds/zero-MISSION.md"
61
- sourcePath: "src/seeds/zero-main.js"
62
- testsPath: "src/seeds/zero-main.test.js"
63
- dependenciesFilepath: "src/seeds/zero-package.json"
64
-
65
- intentionBot:
66
- intentionFilepath: "intentïon.md"
@@ -1,457 +0,0 @@
1
- // SPDX-License-Identifier: GPL-3.0-only
2
- // Copyright (C) 2025-2026 Polycode Limited
3
- // src/copilot/context.js — Context gathering and user prompt assembly
4
- //
5
- // Builds user prompts for each agent type from available local and GitHub context.
6
- // Works with or without GitHub data — local-only context is always sufficient.
7
-
8
- import { resolve } from "path";
9
- import { execSync } from "child_process";
10
- import { scanDirectory, readOptionalFile, extractFeatureSummary, formatPathsSection, summariseIssue, filterIssues } from "./session.js";
11
- import { readCumulativeCost } from "./telemetry.js";
12
- import { defaultLogger } from "./logger.js";
13
-
14
- /**
15
- * Per-agent refinements that control context precision and prompt quality.
16
- * These restore the tight scoping that existed in the old per-task handlers.
17
- */
18
- const AGENT_REFINEMENTS = {
19
- "agent-issue-resolution": {
20
- sortFeatures: "incomplete-first",
21
- includeWebFiles: true,
22
- highlightTargetIssue: true,
23
- trackPromptBudget: true,
24
- },
25
- "agent-apply-fix": {
26
- emphasizeTestOutput: true,
27
- },
28
- "agent-maintain-features": {
29
- sortFeatures: "incomplete-first",
30
- injectLimit: "features",
31
- },
32
- "agent-maintain-library": {
33
- checkSourcesForUrls: true,
34
- injectLimit: "library",
35
- },
36
- };
37
-
38
- /**
39
- * Context requirements per agent. Defines what context each agent needs.
40
- * All fields are optional — the builder includes whatever is available.
41
- */
42
- const AGENT_CONTEXT = {
43
- "agent-iterate": { mission: true, source: true, tests: true, features: true },
44
- "agent-discovery": { source: true, tests: true },
45
- "agent-issue-resolution": { mission: true, source: true, tests: true, features: true, issues: true },
46
- "agent-apply-fix": { source: true, tests: true },
47
- "agent-maintain-features": { mission: true, features: true, issues: true },
48
- "agent-maintain-library": { library: true, librarySources: true },
49
- "agent-ready-issue": { mission: true, features: true, issues: true },
50
- "agent-review-issue": { source: true, tests: true, issues: true },
51
- "agent-discussion-bot": { mission: true, features: true },
52
- "agent-supervisor": { mission: true, features: true, issues: true },
53
- "agent-director": { mission: true, features: true, issues: true, source: true, tests: true },
54
- };
55
-
56
- /**
57
- * Gather local context from the workspace filesystem.
58
- *
59
- * @param {string} workspacePath - Path to the workspace
60
- * @param {Object} config - Parsed agentic config (from config.js)
61
- * @param {Object} [options]
62
- * @param {Object} [options.logger]
63
- * @returns {Object} Context object with all available local data
64
- */
65
- export function gatherLocalContext(workspacePath, config, { logger = defaultLogger } = {}) {
66
- const wsPath = resolve(workspacePath);
67
- const paths = config.paths || {};
68
- const tuning = config.tuning || {};
69
-
70
- const context = {};
71
-
72
- // Mission
73
- const missionPath = paths.mission?.path || "MISSION.md";
74
- context.mission = readOptionalFile(resolve(wsPath, missionPath));
75
-
76
- // Source files
77
- const sourcePath = paths.source?.path || "src/lib/";
78
- const sourceDir = resolve(wsPath, sourcePath);
79
- context.sourceFiles = scanDirectory(sourceDir, [".js", ".ts", ".mjs", ".cjs"], {
80
- fileLimit: tuning.sourceScan || 10,
81
- contentLimit: tuning.sourceContent || 5000,
82
- sortByMtime: true,
83
- clean: true,
84
- outline: true,
85
- }, logger);
86
-
87
- // Test files
88
- const testsPath = paths.tests?.path || "tests/";
89
- const testsDir = resolve(wsPath, testsPath);
90
- context.testFiles = scanDirectory(testsDir, [".js", ".ts", ".test.js", ".test.ts", ".spec.js"], {
91
- fileLimit: tuning.sourceScan || 10,
92
- contentLimit: tuning.testContent || 3000,
93
- sortByMtime: true,
94
- clean: true,
95
- }, logger);
96
-
97
- // Features
98
- const featuresPath = paths.features?.path || "features/";
99
- const featuresDir = resolve(wsPath, featuresPath);
100
- const featureFiles = scanDirectory(featuresDir, [".md"], {
101
- fileLimit: tuning.featuresScan || 10,
102
- sortByMtime: true,
103
- }, logger);
104
- context.features = featureFiles.map((f) => extractFeatureSummary(f.content, f.name));
105
-
106
- // Library
107
- const libraryPath = paths.library?.path || "library/";
108
- const libraryDir = resolve(wsPath, libraryPath);
109
- context.libraryFiles = scanDirectory(libraryDir, [".md"], {
110
- fileLimit: 10,
111
- contentLimit: tuning.documentSummary || 2000,
112
- }, logger);
113
-
114
- // Library sources
115
- const sourcesPath = paths.librarySources?.path || "SOURCES.md";
116
- context.librarySources = readOptionalFile(resolve(wsPath, sourcesPath));
117
-
118
- // Web files
119
- const webPath = paths.web?.path || "src/web/";
120
- const webDir = resolve(wsPath, webPath);
121
- context.webFiles = scanDirectory(webDir, [".html", ".css", ".js"], {
122
- fileLimit: tuning.sourceScan || 10,
123
- contentLimit: tuning.sourceContent || 5000,
124
- sortByMtime: true,
125
- clean: true,
126
- }, logger);
127
-
128
- // Contributing guide
129
- const contributingPath = paths.contributing?.path || "CONTRIBUTING.md";
130
- context.contributing = readOptionalFile(resolve(wsPath, contributingPath), 2000);
131
-
132
- // Package.json
133
- context.packageJson = config.packageJson || readOptionalFile(resolve(wsPath, "package.json"), 3000);
134
-
135
- // Config TOML
136
- context.configToml = config.configToml || "";
137
-
138
- // Paths
139
- context.writablePaths = config.writablePaths || [];
140
- context.readOnlyPaths = config.readOnlyPaths || [];
141
-
142
- // Initial test output
143
- try {
144
- context.testOutput = execSync("npm test 2>&1", { cwd: wsPath, encoding: "utf8", timeout: 120000 });
145
- } catch (err) {
146
- context.testOutput = `STDOUT:\n${err.stdout || ""}\nSTDERR:\n${err.stderr || ""}`;
147
- }
148
-
149
- return context;
150
- }
151
-
152
- /**
153
- * Fetch GitHub context using the `gh` CLI.
154
- * Returns null fields when gh is unavailable or data can't be fetched.
155
- *
156
- * @param {Object} options
157
- * @param {number} [options.issueNumber] - Issue number to fetch
158
- * @param {number} [options.prNumber] - PR number to fetch
159
- * @param {string} [options.discussionUrl] - Discussion URL to fetch
160
- * @param {string} [options.workspacePath] - CWD for gh commands
161
- * @param {Object} [options.logger]
162
- * @returns {Object} GitHub context
163
- */
164
- export function gatherGitHubContext({ issueNumber, prNumber, discussionUrl, workspacePath, logger = defaultLogger } = {}) {
165
- const github = { issues: [], issueDetail: null, prDetail: null, discussionDetail: null };
166
- const cwd = workspacePath || process.cwd();
167
-
168
- try {
169
- // Fetch open issues list
170
- const issuesJson = execSync("gh issue list --state open --limit 20 --json number,title,labels,body,createdAt,updatedAt", {
171
- cwd,
172
- encoding: "utf8",
173
- timeout: 30000,
174
- });
175
- const rawIssues = JSON.parse(issuesJson);
176
- github.issues = filterIssues(rawIssues.map((i) => ({
177
- number: i.number,
178
- title: i.title,
179
- body: i.body,
180
- labels: i.labels,
181
- created_at: i.createdAt,
182
- updated_at: i.updatedAt,
183
- })));
184
- } catch (err) {
185
- logger.info(`[context] Could not fetch issues: ${err.message}`);
186
- }
187
-
188
- // Fetch specific issue detail
189
- if (issueNumber) {
190
- try {
191
- const issueJson = execSync(`gh issue view ${issueNumber} --json number,title,body,labels,comments,createdAt`, {
192
- cwd,
193
- encoding: "utf8",
194
- timeout: 30000,
195
- });
196
- github.issueDetail = JSON.parse(issueJson);
197
- } catch (err) {
198
- logger.info(`[context] Could not fetch issue #${issueNumber}: ${err.message}`);
199
- }
200
- }
201
-
202
- // Fetch specific PR detail
203
- if (prNumber) {
204
- try {
205
- const prJson = execSync(`gh pr view ${prNumber} --json number,title,body,files,statusCheckRollup`, {
206
- cwd,
207
- encoding: "utf8",
208
- timeout: 30000,
209
- });
210
- github.prDetail = JSON.parse(prJson);
211
- } catch (err) {
212
- logger.info(`[context] Could not fetch PR #${prNumber}: ${err.message}`);
213
- }
214
- }
215
-
216
- // Fetch discussion
217
- if (discussionUrl) {
218
- try {
219
- // Extract discussion number from URL
220
- const match = discussionUrl.match(/discussions\/(\d+)/);
221
- if (match) {
222
- const num = match[1];
223
- const discussionJson = execSync(
224
- `gh api graphql -f query='{ repository(owner:"{owner}", name:"{repo}") { discussion(number: ${num}) { title body comments(last: 10) { nodes { body author { login } createdAt } } } } }'`,
225
- { cwd, encoding: "utf8", timeout: 30000 },
226
- );
227
- github.discussionDetail = JSON.parse(discussionJson);
228
- }
229
- } catch (err) {
230
- logger.info(`[context] Could not fetch discussion: ${err.message}`);
231
- }
232
- }
233
-
234
- return github;
235
- }
236
-
237
- /**
238
- * Build a user prompt for the given agent from available context.
239
- *
240
- * Returns an object with the assembled prompt and optional promptBudget metadata.
241
- * The prompt includes config-driven limits and per-agent refinements that restore
242
- * the context precision from the old per-task handlers.
243
- *
244
- * @param {string} agentName - Agent name (e.g. "agent-iterate")
245
- * @param {Object} localContext - From gatherLocalContext()
246
- * @param {Object} [githubContext] - From gatherGitHubContext() (optional)
247
- * @param {Object} [options]
248
- * @param {Object} [options.tuning] - Tuning config for limits
249
- * @param {Object} [options.config] - Full parsed config (for limits injection)
250
- * @returns {{ prompt: string, promptBudget: Array|null }}
251
- */
252
- export function buildUserPrompt(agentName, localContext, githubContext, { tuning, config } = {}) {
253
- const needs = AGENT_CONTEXT[agentName] || AGENT_CONTEXT["agent-iterate"];
254
- const refinements = AGENT_REFINEMENTS[agentName] || {};
255
- const sections = [];
256
- const promptBudget = refinements.trackPromptBudget ? [] : null;
257
-
258
- // Target issue — placed prominently when highlightTargetIssue is set
259
- if (refinements.highlightTargetIssue && githubContext?.issueDetail) {
260
- const issue = githubContext.issueDetail;
261
- const issueSection = [
262
- `# Target Issue #${issue.number}: ${issue.title}`,
263
- issue.body || "(no description)",
264
- `Labels: ${(issue.labels || []).map((l) => typeof l === "string" ? l : l.name).join(", ") || "none"}`,
265
- "",
266
- "**Focus your transformation on resolving this specific issue.**",
267
- ];
268
- if (issue.comments?.length > 0) {
269
- issueSection.push("\n## Comments");
270
- for (const c of issue.comments.slice(-5)) {
271
- issueSection.push(`**${c.author?.login || "unknown"}**: ${c.body}`);
272
- }
273
- }
274
- const text = issueSection.join("\n");
275
- sections.push(text);
276
- if (promptBudget) promptBudget.push({ section: "target-issue", size: text.length, files: "1", notes: "" });
277
- }
278
-
279
- // Mission
280
- if (needs.mission && localContext.mission) {
281
- const text = `# Mission\n\n${localContext.mission}`;
282
- sections.push(text);
283
- if (promptBudget) promptBudget.push({ section: "mission", size: localContext.mission.length, files: "1", notes: "full" });
284
- }
285
-
286
- // Current test state — emphasised for fix-code agent
287
- if (localContext.testOutput) {
288
- const testPreview = localContext.testOutput.substring(0, 4000);
289
- if (refinements.emphasizeTestOutput) {
290
- sections.push(`# Failing Test Output\n\nThe tests are currently failing. Fix the root cause.\n\n\`\`\`\n${testPreview}\n\`\`\``);
291
- } else {
292
- sections.push(`# Current Test State\n\n\`\`\`\n${testPreview}\n\`\`\``);
293
- }
294
- }
295
-
296
- // Source files
297
- if (needs.source && localContext.sourceFiles?.length > 0) {
298
- const sourceSection = [`# Source Files (${localContext.sourceFiles.length})`];
299
- for (const f of localContext.sourceFiles) {
300
- sourceSection.push(`## ${f.name}\n\`\`\`\n${f.content}\n\`\`\``);
301
- }
302
- const text = sourceSection.join("\n\n");
303
- sections.push(text);
304
- if (promptBudget) promptBudget.push({ section: "source", size: text.length, files: `${localContext.sourceFiles.length}`, notes: "" });
305
- }
306
-
307
- // Web files — only when refinements request it
308
- if (refinements.includeWebFiles && localContext.webFiles?.length > 0) {
309
- const webSection = [`# Website Files (${localContext.webFiles.length})`];
310
- for (const f of localContext.webFiles) {
311
- webSection.push(`## ${f.name}\n\`\`\`\n${f.content}\n\`\`\``);
312
- }
313
- const text = webSection.join("\n\n");
314
- sections.push(text);
315
- if (promptBudget) promptBudget.push({ section: "web", size: text.length, files: `${localContext.webFiles.length}`, notes: "" });
316
- }
317
-
318
- // Test files
319
- if (needs.tests && localContext.testFiles?.length > 0) {
320
- const testSection = [`# Test Files (${localContext.testFiles.length})`];
321
- for (const f of localContext.testFiles) {
322
- testSection.push(`## ${f.name}\n\`\`\`\n${f.content}\n\`\`\``);
323
- }
324
- const text = testSection.join("\n\n");
325
- sections.push(text);
326
- if (promptBudget) promptBudget.push({ section: "tests", size: text.length, files: `${localContext.testFiles.length}`, notes: "" });
327
- }
328
-
329
- // Features — sorted incomplete-first when refinements request it
330
- if (needs.features && localContext.features?.length > 0) {
331
- let features = [...localContext.features];
332
- if (refinements.sortFeatures === "incomplete-first") {
333
- features.sort((a, b) => {
334
- const aIncomplete = /Remaining:/.test(a) || /\[ \]/.test(a) ? 0 : 1;
335
- const bIncomplete = /Remaining:/.test(b) || /\[ \]/.test(b) ? 0 : 1;
336
- return aIncomplete - bIncomplete;
337
- });
338
- }
339
-
340
- const limit = config?.paths?.features?.limit;
341
- const header = limit
342
- ? `# Features (${features.length}/${limit} max)`
343
- : `# Features (${features.length})`;
344
- const featureSection = [header];
345
- for (const f of features) {
346
- featureSection.push(f);
347
- }
348
- const text = featureSection.join("\n\n");
349
- sections.push(text);
350
- if (promptBudget) promptBudget.push({ section: "features", size: text.length, files: `${features.length}`, notes: "" });
351
- }
352
-
353
- // Library
354
- if (needs.library && localContext.libraryFiles?.length > 0) {
355
- const libLimit = config?.paths?.library?.limit;
356
- const header = libLimit
357
- ? `# Library Files (${localContext.libraryFiles.length}/${libLimit} max)`
358
- : `# Library Files (${localContext.libraryFiles.length})`;
359
- const libSection = [header];
360
- for (const f of localContext.libraryFiles) {
361
- libSection.push(`## ${f.name}\n${f.content}`);
362
- }
363
- sections.push(libSection.join("\n\n"));
364
- }
365
-
366
- // Library sources — vary strategy based on URL presence
367
- if (needs.librarySources && localContext.librarySources) {
368
- if (refinements.checkSourcesForUrls) {
369
- const hasUrls = /https?:\/\//.test(localContext.librarySources);
370
- if (!hasUrls) {
371
- sections.push(`# Sources\n\n${localContext.librarySources}\n\nPopulate SOURCES.md with 3-8 relevant reference URLs aligned with the mission.`);
372
- } else {
373
- sections.push(`# Sources\n\n${localContext.librarySources}`);
374
- }
375
- } else {
376
- sections.push(`# Sources\n\n${localContext.librarySources}`);
377
- }
378
- }
379
-
380
- // Issues (from GitHub context) — not duplicated if target issue already shown
381
- if (needs.issues && githubContext?.issues?.length > 0) {
382
- const issueSection = [`# Open Issues (${githubContext.issues.length})`];
383
- for (const issue of githubContext.issues) {
384
- issueSection.push(summariseIssue(issue, tuning?.issueBodyLimit || 500));
385
- }
386
- const text = issueSection.join("\n\n");
387
- sections.push(text);
388
- if (promptBudget) promptBudget.push({ section: "issues", size: text.length, files: `${githubContext.issues.length}`, notes: "" });
389
- }
390
-
391
- // Specific issue detail (only if not already highlighted as target issue)
392
- if (!refinements.highlightTargetIssue && githubContext?.issueDetail) {
393
- const issue = githubContext.issueDetail;
394
- const issueSection = [`# Issue #${issue.number}: ${issue.title}\n\n${issue.body || "(no body)"}`];
395
- if (issue.comments?.length > 0) {
396
- issueSection.push("## Comments");
397
- for (const c of issue.comments.slice(-10)) {
398
- issueSection.push(`**${c.author?.login || "unknown"}**: ${c.body}`);
399
- }
400
- }
401
- sections.push(issueSection.join("\n\n"));
402
- }
403
-
404
- // Specific PR detail
405
- if (githubContext?.prDetail) {
406
- const pr = githubContext.prDetail;
407
- const prSection = [`# PR #${pr.number}: ${pr.title}\n\n${pr.body || "(no body)"}`];
408
- if (pr.files?.length > 0) {
409
- prSection.push(`## Changed Files\n${pr.files.map((f) => `- ${f.path}`).join("\n")}`);
410
- }
411
- sections.push(prSection.join("\n\n"));
412
- }
413
-
414
- // File paths section
415
- if (localContext.writablePaths?.length > 0 || localContext.readOnlyPaths?.length > 0) {
416
- sections.push(formatPathsSection(
417
- localContext.writablePaths || [],
418
- localContext.readOnlyPaths || [],
419
- { configToml: localContext.configToml, packageJson: localContext.packageJson },
420
- ));
421
- }
422
-
423
- // Limits section — inject concrete numbers from agentic-lib.toml
424
- if (config) {
425
- const limitsLines = ["# Limits (from agentic-lib.toml)", ""];
426
- const budget = config.transformationBudget || 0;
427
- const featLimit = config.paths?.features?.limit;
428
- const libLimit = config.paths?.library?.limit;
429
-
430
- if (featLimit) limitsLines.push(`- Maximum feature files: ${featLimit}`);
431
- if (libLimit) limitsLines.push(`- Maximum library documents: ${libLimit}`);
432
- if (budget > 0) {
433
- const intentionPath = config.intentionBot?.intentionFilepath;
434
- const cumulativeCost = readCumulativeCost(intentionPath);
435
- const remaining = Math.max(0, budget - cumulativeCost);
436
- limitsLines.push(`- Transformation budget: ${budget} (used: ${cumulativeCost}, remaining: ${remaining})`);
437
- }
438
- if (config.featureDevelopmentIssuesWipLimit) limitsLines.push(`- Maximum concurrent feature issues: ${config.featureDevelopmentIssuesWipLimit}`);
439
- if (config.maintenanceIssuesWipLimit) limitsLines.push(`- Maximum concurrent maintenance issues: ${config.maintenanceIssuesWipLimit}`);
440
- if (config.attemptsPerBranch) limitsLines.push(`- Maximum attempts per branch: ${config.attemptsPerBranch}`);
441
- if (config.attemptsPerIssue) limitsLines.push(`- Maximum attempts per issue: ${config.attemptsPerIssue}`);
442
-
443
- if (limitsLines.length > 2) {
444
- sections.push(limitsLines.join("\n"));
445
- }
446
- }
447
-
448
- // Instructions
449
- sections.push([
450
- "Implement this mission. Read the existing source code and tests,",
451
- "make the required changes, run run_tests to verify, and iterate until all tests pass.",
452
- "",
453
- "Start by reading the existing files, then implement the solution.",
454
- ].join("\n"));
455
-
456
- return { prompt: sections.join("\n\n"), promptBudget };
457
- }