@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
@@ -37,7 +37,7 @@ const TASK_AGENT_MAP = {
37
37
  "maintain-library": "agent-maintain-library",
38
38
  };
39
39
  const INIT_COMMANDS = ["init", "update", "reset"];
40
- const ALL_COMMANDS = [...INIT_COMMANDS, ...TASK_COMMANDS, "version", "mcp", "iterate"];
40
+ const ALL_COMMANDS = [...INIT_COMMANDS, ...TASK_COMMANDS, "version", "iterate"];
41
41
 
42
42
  const HELP = `
43
43
  @xn-intenton-z2a/agentic-lib — Agentic Coding Systems SDK
@@ -61,9 +61,6 @@ Iterator:
61
61
  iterate --here Discover the project and generate a MISSION.md, then iterate
62
62
  iterate --list-missions List available built-in mission seeds
63
63
 
64
- MCP Server:
65
- mcp Start MCP server (for Claude Code, Cursor, etc.)
66
-
67
64
  Options:
68
65
  --purge Full reset — clear features, activity log, source code
69
66
  --reseed Clear features + activity log (keep source code)
@@ -135,13 +132,6 @@ const discussionUrl = discussionIdx >= 0 ? flags[discussionIdx + 1] : "";
135
132
 
136
133
  // ─── Task Commands ───────────────────────────────────────────────────
137
134
 
138
- if (command === "mcp") {
139
- const { startServer } = await import("../src/mcp/server.js");
140
- await startServer();
141
- // Server runs until stdin closes — don't exit
142
- await new Promise(() => {}); // block forever
143
- }
144
-
145
135
  if (command === "iterate") {
146
136
  process.exit(await runIterate());
147
137
  }
@@ -215,10 +205,10 @@ async function runIterate() {
215
205
  console.log("");
216
206
 
217
207
  const discoveryPrompt = loadAgentPrompt("agent-discovery");
218
- const { runHybridSession } = await import("../src/copilot/hybrid-session.js");
208
+ const { runCopilotSession } = await import("../src/copilot/copilot-session.js");
219
209
  const effectiveModel = model || config.model || "gpt-5-mini";
220
210
 
221
- const discoveryResult = await runHybridSession({
211
+ const discoveryResult = await runCopilotSession({
222
212
  workspacePath: target,
223
213
  model: effectiveModel,
224
214
  tuning: config.tuning || {},
@@ -298,8 +288,8 @@ async function runIterate() {
298
288
  );
299
289
  }
300
290
 
301
- const { runHybridSession } = await import("../src/copilot/hybrid-session.js");
302
- const { gatherLocalContext, gatherGitHubContext, buildUserPrompt } = await import("../src/copilot/context.js");
291
+ const { runCopilotSession } = await import("../src/copilot/copilot-session.js");
292
+ const { readOptionalFile } = await import("../src/copilot/session.js");
303
293
 
304
294
  // Load agent prompt: --agent flag > default agent-iterate
305
295
  const agentName = agentFlag || "agent-iterate";
@@ -322,32 +312,26 @@ async function runIterate() {
322
312
  if (discussionUrl) console.log(`Discussion: ${discussionUrl}`);
323
313
  console.log("");
324
314
 
325
- // Gather context for the agent
326
- const localContext = gatherLocalContext(target, config);
327
-
328
- // Optionally gather GitHub context
329
- let githubContext;
330
- if (issueNumber || prNumber || discussionUrl) {
331
- console.log("Fetching GitHub context...");
332
- githubContext = gatherGitHubContext({
333
- issueNumber: issueNumber || undefined,
334
- prNumber: prNumber || undefined,
335
- discussionUrl: discussionUrl || undefined,
336
- workspacePath: target,
337
- });
338
- }
339
-
340
- // Build context-aware user prompt
341
- const { prompt: userPrompt } = buildUserPrompt(agentName, localContext, githubContext, {
342
- tuning: config.tuning,
343
- config,
344
- });
315
+ // Build lean prompt the model explores via tools (read_file, list_files, run_command)
316
+ const missionPath = resolve(target, config.paths?.mission?.path || "MISSION.md");
317
+ const missionContent = readOptionalFile(missionPath, 2000) || "(no mission defined)";
318
+ const userPrompt = [
319
+ "## Mission",
320
+ missionContent,
321
+ "",
322
+ "## Your Task",
323
+ "Use list_files to explore the repository, read_file to examine source code and tests,",
324
+ "and run_command to run the test suite. Write code to advance the mission.",
325
+ ...(issueNumber ? [``, `Focus on issue #${issueNumber}.`] : []),
326
+ ...(prNumber ? [``, `Focus on PR #${prNumber}.`] : []),
327
+ ...(discussionUrl ? [``, `Discussion context: ${discussionUrl}`] : []),
328
+ ].join("\n");
345
329
 
346
330
  // Derive maxToolCalls from transformation budget
347
331
  const budget = config.transformationBudget || 0;
348
332
  const effectiveMaxToolCalls = budget > 0 ? budget * 20 : undefined;
349
333
 
350
- const result = await runHybridSession({
334
+ const result = await runCopilotSession({
351
335
  workspacePath: target,
352
336
  model: effectiveModel,
353
337
  tuning: config.tuning || {},
@@ -423,31 +407,31 @@ async function runTask(taskName) {
423
407
 
424
408
  try {
425
409
  const { loadAgentPrompt } = await import("../src/copilot/agents.js");
426
- const { runHybridSession } = await import("../src/copilot/hybrid-session.js");
427
- const { gatherLocalContext, gatherGitHubContext, buildUserPrompt } = await import("../src/copilot/context.js");
410
+ const { runCopilotSession } = await import("../src/copilot/copilot-session.js");
411
+ const { readOptionalFile } = await import("../src/copilot/session.js");
428
412
 
429
413
  const agentPrompt = loadAgentPrompt(agentName);
430
- const localContext = gatherLocalContext(target, config);
431
-
432
- let githubContext;
433
- if (issueNumber || prNumber) {
434
- githubContext = gatherGitHubContext({
435
- issueNumber: issueNumber || undefined,
436
- prNumber: prNumber || undefined,
437
- workspacePath: target,
438
- });
439
- }
440
414
 
441
- const { prompt: userPrompt, promptBudget } = buildUserPrompt(agentName, localContext, githubContext, {
442
- tuning: config.tuning,
443
- config,
444
- });
415
+ // Build lean prompt the model explores via tools (read_file, list_files, run_command)
416
+ const missionPath = resolve(target, config.paths?.mission?.path || config.paths?.mission || "MISSION.md");
417
+ const missionContent = readOptionalFile(missionPath, 2000) || "(no mission defined)";
418
+ const userPrompt = [
419
+ "## Mission",
420
+ missionContent,
421
+ "",
422
+ "## Your Task",
423
+ "Use list_files to explore the repository, read_file to examine source code and tests,",
424
+ "and run_command to run the test suite. Write code to advance the mission.",
425
+ ...(issueNumber ? [``, `Focus on issue #${issueNumber}.`] : []),
426
+ ...(prNumber ? [``, `Focus on PR #${prNumber}.`] : []),
427
+ ...(discussionUrl ? [``, `Discussion context: ${discussionUrl}`] : []),
428
+ ].join("\n");
445
429
 
446
430
  // Derive maxToolCalls from transformation budget (budget × 20, or unlimited)
447
431
  const budget = config.transformationBudget || 0;
448
432
  const effectiveMaxToolCalls = budget > 0 ? budget * 20 : undefined;
449
433
 
450
- const result = await runHybridSession({
434
+ const result = await runCopilotSession({
451
435
  workspacePath: target,
452
436
  model: effectiveModel,
453
437
  tuning: config.tuning || {},
@@ -469,12 +453,6 @@ async function runTask(taskName) {
469
453
  console.log(`Tool calls: ${result.toolCalls}`);
470
454
  console.log(`Tokens: ${tokensUsed} (in=${result.tokensIn} out=${result.tokensOut})`);
471
455
  if (result.narrative) console.log(`Narrative: ${result.narrative}`);
472
- if (promptBudget) {
473
- console.log("Prompt budget:");
474
- for (const entry of promptBudget) {
475
- console.log(` ${entry.section}: ${entry.size} chars, ${entry.files} files ${entry.notes}`);
476
- }
477
- }
478
456
  console.log("");
479
457
  return result.success ? 0 : 1;
480
458
  } catch (err) {
@@ -575,6 +553,16 @@ function initActions(agenticDir) {
575
553
  }
576
554
  }
577
555
 
556
+ function initAgents(dstDir) {
557
+ console.log("\n--- Agents ---");
558
+ const agentsSrcDir = resolve(pkgRoot, ".github/agents");
559
+ if (!existsSync(agentsSrcDir)) return;
560
+ for (const entry of readdirSync(agentsSrcDir, { withFileTypes: true })) {
561
+ if (!entry.isFile()) continue;
562
+ initCopyFile(resolve(agentsSrcDir, entry.name), resolve(dstDir, entry.name), `agents/${entry.name}`);
563
+ }
564
+ }
565
+
578
566
  function initDirContents(srcSubdir, dstDir, label, excludeFiles = []) {
579
567
  console.log(`\n--- ${label} ---`);
580
568
  const dir = resolve(srcDir, srcSubdir);
@@ -770,6 +758,7 @@ function initPurge(seedsDir, missionName, initTimestamp) {
770
758
  "zero-package.json": "package.json",
771
759
  "zero-README.md": "README.md",
772
760
  "zero-.gitignore": ".gitignore",
761
+ "zero-SCREENSHOT_INDEX.png": "SCREENSHOT_INDEX.png",
773
762
  };
774
763
  for (const [seedFile, targetRel] of Object.entries(SEED_MAP)) {
775
764
  const src = resolve(seedsDir, seedFile);
@@ -1271,7 +1260,7 @@ function runInit() {
1271
1260
  initWorkflows();
1272
1261
  initActions(agenticDir);
1273
1262
  initDirContents("copilot", resolve(agenticDir, "copilot"), "Copilot (shared modules)");
1274
- initDirContents("agents", resolve(target, ".github/agents"), "Agents", ["agentic-lib.yml"]);
1263
+ initAgents(resolve(target, ".github/agents"));
1275
1264
  // Remove stale legacy agents directory
1276
1265
  const legacyAgentsDir = resolve(agenticDir, "agents");
1277
1266
  if (existsSync(legacyAgentsDir)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xn-intenton-z2a/agentic-lib",
3
- "version": "7.4.8",
3
+ "version": "7.4.9",
4
4
  "description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -79,10 +79,9 @@
79
79
  "src/actions/agentic-step/tasks/",
80
80
  "src/actions/commit-if-changed/",
81
81
  "src/actions/setup-npmrc/",
82
- "src/agents/",
82
+ ".github/agents/",
83
83
  "src/seeds/",
84
- "src/scripts/",
85
- "src/mcp/"
84
+ "src/scripts/"
86
85
  ],
87
86
  "overrides": {
88
87
  "minimatch": ">=10.2.3",
@@ -16,7 +16,7 @@ inputs:
16
16
  maintain-library, enhance-issue, review-issue, discussions, supervise
17
17
  required: true
18
18
  config:
19
- description: "Path to agentic-lib.yml configuration file"
19
+ description: "Path to agentic-lib.toml configuration file"
20
20
  required: false
21
21
  default: "agentic-lib.toml"
22
22
  instructions:
@@ -39,7 +39,6 @@ import {
39
39
  scanDirectory as _scanDirectory,
40
40
  buildClientOptions as _buildClientOptions,
41
41
  logTuningParam as _logTuningParam,
42
- runCopilotTask as _runCopilotTask,
43
42
  } from "../../copilot/session.js";
44
43
 
45
44
  export function readOptionalFile(filePath, limit) {
@@ -57,7 +56,3 @@ export function buildClientOptions(githubToken) {
57
56
  export function logTuningParam(param, value, profileName, model, clip) {
58
57
  return _logTuningParam(param, value, profileName, model, clip, actionsLogger);
59
58
  }
60
-
61
- export async function runCopilotTask(options) {
62
- return _runCopilotTask({ ...options, logger: actionsLogger });
63
- }
@@ -9,7 +9,7 @@ import * as core from "@actions/core";
9
9
  import * as github from "@actions/github";
10
10
  import { loadConfig, getWritablePaths } from "./config-loader.js";
11
11
  import { logActivity, generateClosingNotes } from "./logging.js";
12
- import { readFileSync } from "fs";
12
+ import { readFileSync, existsSync } from "fs";
13
13
  import {
14
14
  buildMissionMetrics, buildMissionReadiness,
15
15
  computeTransformationCost, readCumulativeCost, buildLimitsStatus,
@@ -60,6 +60,12 @@ async function run() {
60
60
  const handler = TASKS[task];
61
61
  if (!handler) throw new Error(`Unknown task: ${task}. Available: ${Object.keys(TASKS).join(", ")}`);
62
62
 
63
+ // Resolve log and screenshot paths (fetched from agentic-lib-logs branch by workflow)
64
+ const logFile = config.intentionBot?.intentionFilepath || "intenti\u00F6n.md";
65
+ const screenshotFile = config.intentionBot?.screenshotFile || "SCREENSHOT_INDEX.png";
66
+ const logFilePath = existsSync(logFile) ? logFile : null;
67
+ const screenshotFilePath = existsSync(screenshotFile) ? screenshotFile : null;
68
+
63
69
  const context = {
64
70
  task, config, instructions, issueNumber, writablePaths, testCommand, model,
65
71
  prNumber: core.getInput("pr-number"),
@@ -68,6 +74,7 @@ async function run() {
68
74
  commentCreatedAt: core.getInput("comment-created-at"),
69
75
  octokit: github.getOctokit(process.env.GITHUB_TOKEN),
70
76
  repo: github.context.repo, github: github.context,
77
+ logFilePath, screenshotFilePath,
71
78
  };
72
79
 
73
80
  const startTime = Date.now();
@@ -5,8 +5,9 @@
5
5
  // Appends structured entries to the intentïon.md activity log,
6
6
  // including commit URLs and safety-check outcomes.
7
7
 
8
- import { writeFileSync, readFileSync, appendFileSync, existsSync, mkdirSync } from "fs";
9
- import { dirname } from "path";
8
+ import { writeFileSync, readFileSync, appendFileSync, existsSync, mkdirSync, copyFileSync } from "fs";
9
+ import { dirname, basename } from "path";
10
+ import { join } from "path";
10
11
  import * as core from "@actions/core";
11
12
 
12
13
  /**
@@ -158,6 +159,17 @@ export function logActivity({
158
159
  } else {
159
160
  writeFileSync(filepath, `# intentïon Activity Log\n${entry}`);
160
161
  }
162
+
163
+ // Write ASCII fallback copy (intention.md) for systems that can't handle UTF-8 filenames
164
+ const name = basename(filepath);
165
+ if (name !== "intention.md" && name.includes("intenti")) {
166
+ const fallbackPath = join(dirname(filepath), "intention.md");
167
+ try {
168
+ copyFileSync(filepath, fallbackPath);
169
+ } catch {
170
+ // Best-effort — don't fail the log if the copy fails
171
+ }
172
+ }
161
173
  }
162
174
 
163
175
  /**
@@ -2,13 +2,15 @@
2
2
  // Copyright (C) 2025-2026 Polycode Limited
3
3
  // tasks/direct.js — Director: mission-complete/failed evaluation via LLM
4
4
  //
5
- // Gathers mission metrics, builds an advisory assessment, asks the LLM
6
- // to decide mission-complete, mission-failed, or produce a gap analysis.
5
+ // Uses runCopilotSession with lean prompts: the model reads source files
6
+ // via tools to determine mission status and produce a structured evaluation.
7
7
  // The director does NOT dispatch workflows or create issues — that's the supervisor's job.
8
8
 
9
9
  import * as core from "@actions/core";
10
10
  import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from "fs";
11
- import { runCopilotTask, readOptionalFile, scanDirectory, filterIssues } from "../copilot.js";
11
+ import { readOptionalFile, scanDirectory, filterIssues, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
12
+ import { runCopilotSession } from "../../../copilot/copilot-session.js";
13
+ import { createGitHubTools, createGitTools } from "../../../copilot/github-tools.js";
12
14
 
13
15
  /**
14
16
  * Count TODO comments recursively in a directory.
@@ -98,7 +100,7 @@ function buildMetricAssessment(ctx, config) {
98
100
  }
99
101
 
100
102
  /**
101
- * Build the director prompt.
103
+ * Build the director prompt (lean version — model explores via tools).
102
104
  */
103
105
  function buildPrompt(ctx, agentInstructions, metricAssessment) {
104
106
  return [
@@ -114,35 +116,21 @@ function buildPrompt(ctx, agentInstructions, metricAssessment) {
114
116
  "### Mission-Complete Metrics",
115
117
  metricAssessment.table,
116
118
  "",
117
- "## Repository State",
118
- `### Open Issues (${ctx.issuesSummary.length})`,
119
- ctx.issuesSummary.join("\n") || "none",
119
+ "## Repository Summary",
120
+ `Open issues: ${ctx.issuesSummary.length}`,
121
+ `Recently closed issues: ${ctx.recentlyClosedSummary.length}`,
122
+ `Open PRs: ${ctx.prsSummary.length}`,
123
+ `Dedicated test files: ${ctx.dedicatedTestCount}`,
124
+ `Source TODOs: ${ctx.sourceTodoCount}`,
125
+ `Transformation budget: ${ctx.cumulativeTransformationCost}/${ctx.transformationBudget || "unlimited"}`,
120
126
  "",
121
- `### Recently Closed Issues (${ctx.recentlyClosedSummary.length})`,
122
- ctx.recentlyClosedSummary.join("\n") || "none",
123
- "",
124
- `### Open PRs (${ctx.prsSummary.length})`,
125
- ctx.prsSummary.join("\n") || "none",
126
- "",
127
- ...(ctx.sourceExports?.length > 0
128
- ? [
129
- `### Source Exports`,
130
- ...ctx.sourceExports.map((e) => `- ${e}`),
131
- "",
132
- ]
133
- : []),
134
- `### Test Coverage`,
135
- ctx.dedicatedTestCount > 0
136
- ? `Dedicated test files (${ctx.dedicatedTestCount}): ${ctx.dedicatedTestFiles.join(", ")}`
137
- : "**No dedicated test files found.**",
138
- "",
139
- `### Source TODO Count: ${ctx.sourceTodoCount}`,
140
- "",
141
- `### Transformation Budget: ${ctx.cumulativeTransformationCost}/${ctx.transformationBudget || "unlimited"}`,
142
- "",
143
- `### Recent Activity`,
144
- ctx.recentActivity || "none",
127
+ "## Your Task",
128
+ "Use list_issues and list_prs to review open work items.",
129
+ "Use read_file to inspect source code and tests for completeness.",
130
+ "Use git_diff or git_status for additional context if needed.",
131
+ "Then call report_director_decision with your determination.",
145
132
  "",
133
+ "**You MUST call report_director_decision exactly once.**",
146
134
  ].join("\n");
147
135
  }
148
136
 
@@ -240,13 +228,12 @@ async function executeMissionFailed(octokit, repo, reason) {
240
228
  * @returns {Promise<Object>} Result with outcome, tokensUsed, model
241
229
  */
242
230
  export async function direct(context) {
243
- const { octokit, repo, config, instructions, model } = context;
231
+ const { octokit, repo, config, instructions, model, logFilePath, screenshotFilePath } = context;
244
232
  const t = config.tuning || {};
245
233
 
246
234
  // --- Gather context (similar to supervisor but focused on metrics) ---
247
235
  const mission = readOptionalFile(config.paths.mission.path);
248
236
  const intentionLogFull = readOptionalFile(config.intentionBot.intentionFilepath);
249
- const recentActivity = intentionLogFull.split("\n").slice(-20).join("\n");
250
237
 
251
238
  const costMatches = intentionLogFull.matchAll(/\*\*agentic-lib transformation cost:\*\* (\d+)/g);
252
239
  const cumulativeTransformationCost = [...costMatches].reduce((sum, m) => sum + parseInt(m[1], 10), 0);
@@ -271,7 +258,7 @@ export async function direct(context) {
271
258
  const initTimestamp = config.init?.timestamp || null;
272
259
 
273
260
  const { data: openIssues } = await octokit.rest.issues.listForRepo({
274
- ...repo, state: "open", per_page: t.issuesScan || 20, sort: "created", direction: "asc",
261
+ ...repo, state: "open", per_page: 20, sort: "created", direction: "asc",
275
262
  });
276
263
  const issuesOnly = openIssues.filter((i) => !i.pull_request);
277
264
  const filteredIssues = filterIssues(issuesOnly, { staleDays: t.staleDays || 30, initTimestamp });
@@ -307,7 +294,6 @@ export async function direct(context) {
307
294
  closeReason = "RESOLVED";
308
295
  }
309
296
  }
310
- // Check for automerge closure (issue has "merged" label — set by ci-automerge)
311
297
  if (closeReason !== "RESOLVED") {
312
298
  const issueLabels = ci.labels.map((l) => (typeof l === "string" ? l : l.name));
313
299
  if (issueLabels.includes("merged")) {
@@ -328,23 +314,6 @@ export async function direct(context) {
328
314
  });
329
315
  const prsSummary = openPRs.map((pr) => `#${pr.number}: ${pr.title} (${pr.head.ref})`);
330
316
 
331
- // Source exports
332
- let sourceExports = [];
333
- try {
334
- const sourcePath = config.paths.source?.path || "src/lib/";
335
- if (existsSync(sourcePath)) {
336
- const sourceFiles = scanDirectory(sourcePath, [".js", ".ts"], { limit: 5 });
337
- for (const sf of sourceFiles) {
338
- const content = readFileSync(sf.path, "utf8");
339
- const exports = [...content.matchAll(/export\s+(?:async\s+)?(?:function|const|let|var|class)\s+(\w+)/g)]
340
- .map((m) => m[1]);
341
- if (exports.length > 0) {
342
- sourceExports.push(`${sf.name}: ${exports.join(", ")}`);
343
- }
344
- }
345
- }
346
- } catch { /* ignore */ }
347
-
348
317
  // Dedicated tests
349
318
  const { dedicatedTestCount, dedicatedTestFiles } = detectDedicatedTests();
350
319
 
@@ -357,12 +326,10 @@ export async function direct(context) {
357
326
  // Build context
358
327
  const ctx = {
359
328
  mission,
360
- recentActivity,
361
329
  issuesSummary,
362
330
  recentlyClosedSummary,
363
331
  resolvedCount,
364
332
  prsSummary,
365
- sourceExports,
366
333
  dedicatedTestCount,
367
334
  dedicatedTestFiles,
368
335
  sourceTodoCount,
@@ -374,20 +341,74 @@ export async function direct(context) {
374
341
  const metricAssessment = buildMetricAssessment(ctx, config);
375
342
  core.info(`Metric assessment: ${metricAssessment.assessment}`);
376
343
 
377
- // --- LLM decision ---
344
+ // --- LLM decision via hybrid session ---
378
345
  const agentInstructions = instructions || "You are the director. Evaluate mission readiness.";
379
346
  const prompt = buildPrompt(ctx, agentInstructions, metricAssessment);
380
347
 
381
- const { content, tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
348
+ const systemPrompt =
349
+ "You are the director of an autonomous coding repository. Your job is to evaluate whether the mission is complete, failed, or in progress. You produce a structured assessment — you do NOT dispatch workflows or create issues." +
350
+ NARRATIVE_INSTRUCTION;
351
+
352
+ // Shared mutable state to capture the decision
353
+ const decisionResult = { decision: "in-progress", reason: "", analysis: "" };
354
+
355
+ const createTools = (defineTool, _wp, logger) => {
356
+ const ghTools = createGitHubTools(octokit, repo, defineTool, logger);
357
+ const gitTools = createGitTools(defineTool, logger);
358
+
359
+ const reportDecision = defineTool("report_director_decision", {
360
+ description: "Report the director's mission evaluation decision. Call this exactly once.",
361
+ parameters: {
362
+ type: "object",
363
+ properties: {
364
+ decision: {
365
+ type: "string",
366
+ enum: ["mission-complete", "mission-failed", "in-progress"],
367
+ description: "The mission status decision",
368
+ },
369
+ reason: { type: "string", description: "One-line summary of the decision" },
370
+ analysis: { type: "string", description: "Detailed analysis of the mission state" },
371
+ },
372
+ required: ["decision", "reason"],
373
+ },
374
+ handler: async ({ decision, reason, analysis }) => {
375
+ decisionResult.decision = decision;
376
+ decisionResult.reason = reason || "";
377
+ decisionResult.analysis = analysis || "";
378
+ return { textResultForLlm: `Decision recorded: ${decision}` };
379
+ },
380
+ });
381
+
382
+ return [...ghTools, ...gitTools, reportDecision];
383
+ };
384
+
385
+ const attachments = [];
386
+ if (logFilePath) attachments.push({ type: "file", path: logFilePath });
387
+ if (screenshotFilePath) attachments.push({ type: "file", path: screenshotFilePath });
388
+
389
+ const result = await runCopilotSession({
390
+ workspacePath: process.cwd(),
382
391
  model,
383
- systemMessage:
384
- "You are the director of an autonomous coding repository. Your job is to evaluate whether the mission is complete, failed, or in progress. You produce a structured assessment — you do NOT dispatch workflows or create issues.",
385
- prompt,
386
- writablePaths: [],
387
392
  tuning: t,
393
+ agentPrompt: systemPrompt,
394
+ userPrompt: prompt,
395
+ writablePaths: [],
396
+ createTools,
397
+ attachments,
398
+ excludedTools: ["write_file", "run_command", "run_tests", "dispatch_workflow", "close_issue", "label_issue", "post_discussion_comment", "create_issue", "comment_on_issue"],
399
+ logger: { info: core.info, warning: core.warning, error: core.error, debug: core.debug },
388
400
  });
389
401
 
390
- const { decision, reason, analysis } = parseDirectorResponse(content);
402
+ const tokensUsed = result.tokensIn + result.tokensOut;
403
+
404
+ // Extract decision — prefer tool result, fall back to text parsing
405
+ let { decision, reason, analysis } = decisionResult;
406
+ if (decision === "in-progress" && !decisionResult.reason && result.agentMessage) {
407
+ const parsed = parseDirectorResponse(result.agentMessage);
408
+ decision = parsed.decision;
409
+ reason = parsed.reason;
410
+ analysis = parsed.analysis;
411
+ }
391
412
  core.info(`Director decision: ${decision} — ${reason}`);
392
413
 
393
414
  // Execute the decision
@@ -414,12 +435,12 @@ export async function direct(context) {
414
435
  return {
415
436
  outcome,
416
437
  tokensUsed,
417
- inputTokens,
418
- outputTokens,
419
- cost,
438
+ inputTokens: result.tokensIn,
439
+ outputTokens: result.tokensOut,
440
+ cost: 0,
420
441
  model,
421
442
  details: `Decision: ${decision}\nReason: ${reason}\nAnalysis: ${analysis.substring(0, 300)}`,
422
- narrative: `Director: ${reason}`,
443
+ narrative: result.narrative || `Director: ${reason}`,
423
444
  metricAssessment: metricAssessment.assessment,
424
445
  directorAnalysis: analysis,
425
446
  dedicatedTestCount,