@xn-intenton-z2a/agentic-lib 7.4.4 → 7.4.6

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.
@@ -128,7 +128,11 @@ jobs:
128
128
 
129
129
  - name: Install agentic-step dependencies
130
130
  working-directory: .github/agentic-lib/actions/agentic-step
131
- run: npm ci
131
+ run: |
132
+ npm ci
133
+ if [ -d "../../copilot" ]; then
134
+ ln -sf "$(pwd)/node_modules" ../../copilot/node_modules
135
+ fi
132
136
 
133
137
  - name: Read test command from config
134
138
  id: config
@@ -126,17 +126,20 @@ permissions: write-all
126
126
 
127
127
  jobs:
128
128
  # Step 1: Update agentic-lib and infrastructure (commits to main)
129
+ # Skip tests when purge/reseed will replace all user code anyway
129
130
  update:
130
131
  uses: ./.github/workflows/agentic-lib-update.yml
131
132
  with:
132
133
  ref: ${{ inputs.ref }}
133
134
  dry-run: ${{ inputs.dry-run || 'false' }}
135
+ skip-tests: ${{ inputs.mode == 'reseed' || inputs.mode == 'purge' }}
134
136
  secrets: inherit
135
137
 
136
138
  # Step 2: Reseed/purge and configure (only if mode != update)
139
+ # Use !cancelled() so purge runs even if update's npm test failed
137
140
  init:
138
141
  needs: update
139
- if: inputs.mode == 'reseed' || inputs.mode == 'purge'
142
+ if: "!cancelled() && (inputs.mode == 'reseed' || inputs.mode == 'purge')"
140
143
  runs-on: ubuntu-latest
141
144
  env:
142
145
  INIT_MODE: ${{ inputs.mode }}
@@ -22,6 +22,10 @@ on:
22
22
  type: string
23
23
  required: false
24
24
  default: "false"
25
+ skip-tests:
26
+ type: string
27
+ required: false
28
+ default: "false"
25
29
  #@dist schedule:
26
30
  #@dist - cron: "15 6 * * *"
27
31
  workflow_dispatch:
@@ -76,7 +80,9 @@ jobs:
76
80
  if: hashFiles('src/actions/agentic-step/package.json') != ''
77
81
  run: cd src/actions/agentic-step && npm ci
78
82
 
79
- - run: npm test
83
+ - name: Run tests
84
+ if: inputs.skip-tests != 'true'
85
+ run: npm test
80
86
 
81
87
  - name: Commit and push to main [skip ci]
82
88
  if: github.repository != 'xn-intenton-z2a/agentic-lib' && inputs.dry-run != 'true' && inputs.dry-run != true
@@ -601,7 +601,11 @@ jobs:
601
601
 
602
602
  - name: Install agentic-step dependencies
603
603
  working-directory: .github/agentic-lib/actions/agentic-step
604
- run: npm ci
604
+ run: |
605
+ npm ci
606
+ if [ -d "../../copilot" ]; then
607
+ ln -sf "$(pwd)/node_modules" ../../copilot/node_modules
608
+ fi
605
609
 
606
610
  - name: Apply profile and model to config
607
611
  if: inputs.profile != '' || inputs.model != ''
@@ -713,7 +717,11 @@ jobs:
713
717
 
714
718
  - name: Install agentic-step dependencies
715
719
  working-directory: .github/agentic-lib/actions/agentic-step
716
- run: npm ci
720
+ run: |
721
+ npm ci
722
+ if [ -d "../../copilot" ]; then
723
+ ln -sf "$(pwd)/node_modules" ../../copilot/node_modules
724
+ fi
717
725
 
718
726
  - name: Run director
719
727
  id: director
@@ -764,7 +772,11 @@ jobs:
764
772
 
765
773
  - name: Install agentic-step dependencies
766
774
  working-directory: .github/agentic-lib/actions/agentic-step
767
- run: npm ci
775
+ run: |
776
+ npm ci
777
+ if [ -d "../../copilot" ]; then
778
+ ln -sf "$(pwd)/node_modules" ../../copilot/node_modules
779
+ fi
768
780
 
769
781
  - name: Run supervisor
770
782
  if: github.repository != 'xn-intenton-z2a/agentic-lib'
@@ -814,7 +826,11 @@ jobs:
814
826
 
815
827
  - name: Install agentic-step dependencies
816
828
  working-directory: .github/agentic-lib/actions/agentic-step
817
- run: npm ci
829
+ run: |
830
+ npm ci
831
+ if [ -d "../../copilot" ]; then
832
+ ln -sf "$(pwd)/node_modules" ../../copilot/node_modules
833
+ fi
818
834
 
819
835
  - name: Check mission-complete signal
820
836
  id: fix-mission-check
@@ -1128,7 +1144,11 @@ jobs:
1128
1144
 
1129
1145
  - name: Install agentic-step dependencies
1130
1146
  working-directory: .github/agentic-lib/actions/agentic-step
1131
- run: npm ci
1147
+ run: |
1148
+ npm ci
1149
+ if [ -d "../../copilot" ]; then
1150
+ ln -sf "$(pwd)/node_modules" ../../copilot/node_modules
1151
+ fi
1132
1152
 
1133
1153
  - name: Review issues
1134
1154
  uses: ./.github/agentic-lib/actions/agentic-step
@@ -1186,7 +1206,11 @@ jobs:
1186
1206
 
1187
1207
  - name: Install agentic-step dependencies
1188
1208
  working-directory: .github/agentic-lib/actions/agentic-step
1189
- run: npm ci
1209
+ run: |
1210
+ npm ci
1211
+ if [ -d "../../copilot" ]; then
1212
+ ln -sf "$(pwd)/node_modules" ../../copilot/node_modules
1213
+ fi
1190
1214
 
1191
1215
  - name: Load config
1192
1216
  id: config
package/README.md CHANGED
@@ -390,8 +390,8 @@ agentic-lib iterate --mission 6-kyu-understand-hamming-distance --model gpt-5-mi
390
390
  Target: /tmp/bench
391
391
  Model: gpt-5-mini
392
392
 
393
- [hybrid] Creating session (model=gpt-5-mini, workspace=/tmp/bench)
394
- [hybrid] Session: sess_abc123
393
+ [agentic-lib] Creating session (model=gpt-5-mini, workspace=/tmp/bench)
394
+ [agentic-lib] Session: sess_abc123
395
395
  [tool] read_file
396
396
  [tool] read_file
397
397
  [tool] write_file
@@ -30,6 +30,12 @@ const flags = args.slice(1);
30
30
 
31
31
  let initChanges = 0;
32
32
  const TASK_COMMANDS = ["transform", "maintain-features", "maintain-library", "fix-code"];
33
+ const TASK_AGENT_MAP = {
34
+ "transform": "agent-issue-resolution",
35
+ "fix-code": "agent-apply-fix",
36
+ "maintain-features": "agent-maintain-features",
37
+ "maintain-library": "agent-maintain-library",
38
+ };
33
39
  const INIT_COMMANDS = ["init", "update", "reset"];
34
40
  const ALL_COMMANDS = [...INIT_COMMANDS, ...TASK_COMMANDS, "version", "mcp", "iterate"];
35
41
 
@@ -332,7 +338,14 @@ async function runIterate() {
332
338
  }
333
339
 
334
340
  // Build context-aware user prompt
335
- const userPrompt = buildUserPrompt(agentName, localContext, githubContext, { tuning: config.tuning });
341
+ const { prompt: userPrompt } = buildUserPrompt(agentName, localContext, githubContext, {
342
+ tuning: config.tuning,
343
+ config,
344
+ });
345
+
346
+ // Derive maxToolCalls from transformation budget
347
+ const budget = config.transformationBudget || 0;
348
+ const effectiveMaxToolCalls = budget > 0 ? budget * 20 : undefined;
336
349
 
337
350
  const result = await runHybridSession({
338
351
  workspacePath: target,
@@ -342,6 +355,7 @@ async function runIterate() {
342
355
  agentPrompt,
343
356
  userPrompt,
344
357
  writablePaths: config.writablePaths?.length > 0 ? config.writablePaths : undefined,
358
+ maxToolCalls: effectiveMaxToolCalls,
345
359
  });
346
360
 
347
361
  console.log("");
@@ -366,84 +380,109 @@ async function runIterate() {
366
380
  // ─── Task Runner ─────────────────────────────────────────────────────
367
381
 
368
382
  async function runTask(taskName) {
383
+ // Task commands are now aliases for iterate --agent <agent-name>
384
+ const agentName = TASK_AGENT_MAP[taskName];
385
+ if (!agentName) {
386
+ console.error(`Unknown task: ${taskName}`);
387
+ return 1;
388
+ }
389
+
369
390
  console.log("");
370
- console.log(`=== agentic-lib ${taskName} ===`);
391
+ console.log(`=== agentic-lib ${taskName} (→ iterate --agent ${agentName}) ===`);
371
392
  console.log(`Target: ${target}`);
372
393
  console.log(`Model: ${model}`);
373
394
  console.log(`Dry-run: ${dryRun}`);
374
395
  console.log("");
375
396
 
376
- // Load config from shared module
377
397
  const { loadConfig } = await import("../src/copilot/config.js");
378
- const config = loadConfig(resolve(target, "agentic-lib.toml"));
379
- const effectiveModel = model || config.model;
398
+ let config;
399
+ try {
400
+ config = loadConfig(resolve(target, "agentic-lib.toml"));
401
+ } catch {
402
+ config = { tuning: {}, model: "gpt-5-mini", paths: {}, writablePaths: [], readOnlyPaths: [] };
403
+ }
404
+ const effectiveModel = model || config.model || "gpt-5-mini";
380
405
 
381
- console.log(`[config] supervisor=${config.supervisor}`);
382
- console.log(`[config] writable=${config.writablePaths.join(", ")}`);
383
- console.log(`[config] test=${config.testScript}`);
406
+ console.log(`[config] writable=${(config.writablePaths || []).join(", ")}`);
384
407
  console.log("");
385
408
 
409
+ // Short-circuit guards — skip LLM invocation when unnecessary
410
+ const { checkGuards } = await import("../src/copilot/guards.js");
411
+ const guardResult = checkGuards(taskName, config, target);
412
+ if (guardResult.skip) {
413
+ console.log(`=== ${taskName} skipped (nop) ===`);
414
+ console.log(`Reason: ${guardResult.reason}`);
415
+ console.log("");
416
+ return 0;
417
+ }
418
+
386
419
  if (dryRun) {
387
420
  console.log("=== DRY RUN — task would run but not sending to Copilot ===");
388
421
  return 0;
389
422
  }
390
423
 
391
- // Change to target directory so relative paths in config work
392
- const originalCwd = process.cwd();
393
- process.chdir(target);
394
-
395
424
  try {
396
- const context = {
425
+ 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");
428
+
429
+ 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
+
441
+ const { prompt: userPrompt, promptBudget } = buildUserPrompt(agentName, localContext, githubContext, {
442
+ tuning: config.tuning,
397
443
  config,
398
- writablePaths: config.writablePaths,
444
+ });
445
+
446
+ // Derive maxToolCalls from transformation budget (budget × 20, or unlimited)
447
+ const budget = config.transformationBudget || 0;
448
+ const effectiveMaxToolCalls = budget > 0 ? budget * 20 : undefined;
449
+
450
+ const result = await runHybridSession({
451
+ workspacePath: target,
399
452
  model: effectiveModel,
400
- testCommand: config.testScript,
401
- logger: { info: console.log, warning: console.warn, error: console.error, debug: () => {} },
402
- };
403
-
404
- let result;
405
- switch (taskName) {
406
- case "transform": {
407
- const { transform } = await import("../src/copilot/tasks/transform.js");
408
- result = await transform(context);
409
- break;
410
- }
411
- case "maintain-features": {
412
- const { maintainFeatures } = await import("../src/copilot/tasks/maintain-features.js");
413
- result = await maintainFeatures(context);
414
- break;
415
- }
416
- case "maintain-library": {
417
- const { maintainLibrary } = await import("../src/copilot/tasks/maintain-library.js");
418
- result = await maintainLibrary(context);
419
- break;
420
- }
421
- case "fix-code": {
422
- const { fixCode } = await import("../src/copilot/tasks/fix-code.js");
423
- result = await fixCode(context);
424
- break;
425
- }
426
- default:
427
- console.error(`Unknown task: ${taskName}`);
428
- return 1;
429
- }
453
+ tuning: config.tuning || {},
454
+ timeoutMs,
455
+ agentPrompt,
456
+ userPrompt,
457
+ writablePaths: config.writablePaths?.length > 0 ? config.writablePaths : undefined,
458
+ maxToolCalls: effectiveMaxToolCalls,
459
+ });
460
+
461
+ // Build enriched result (10e)
462
+ const outcome = result.success ? "transformed" : "error";
463
+ const tokensUsed = result.tokensIn + result.tokensOut;
430
464
 
431
465
  console.log("");
432
466
  console.log(`=== ${taskName} completed ===`);
433
- console.log(`Outcome: ${result.outcome}`);
434
- if (result.details) console.log(`Details: ${result.details}`);
435
- if (result.tokensUsed) console.log(`Tokens: ${result.tokensUsed} (in=${result.inputTokens} out=${result.outputTokens})`);
467
+ console.log(`Outcome: ${outcome}`);
468
+ console.log(`Session time: ${result.sessionTime}s`);
469
+ console.log(`Tool calls: ${result.toolCalls}`);
470
+ console.log(`Tokens: ${tokensUsed} (in=${result.tokensIn} out=${result.tokensOut})`);
436
471
  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
+ }
437
478
  console.log("");
438
- return 0;
479
+ return result.success ? 0 : 1;
439
480
  } catch (err) {
440
481
  console.error("");
441
482
  console.error(`=== ${taskName} FAILED ===`);
442
483
  console.error(err.message);
443
484
  if (err.stack) console.error(err.stack);
444
485
  return 1;
445
- } finally {
446
- process.chdir(originalCwd);
447
486
  }
448
487
  }
449
488
 
@@ -1229,6 +1268,7 @@ function runInit() {
1229
1268
 
1230
1269
  initWorkflows();
1231
1270
  initActions(agenticDir);
1271
+ initDirContents("copilot", resolve(agenticDir, "copilot"), "Copilot (shared modules)");
1232
1272
  initDirContents("agents", resolve(agenticDir, "agents"), "Agents");
1233
1273
  initDirContents("seeds", resolve(agenticDir, "seeds"), "Seeds");
1234
1274
  initScripts(agenticDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xn-intenton-z2a/agentic-lib",
3
- "version": "7.4.4",
3
+ "version": "7.4.6",
4
4
  "description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,308 +1,8 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  // Copyright (C) 2025-2026 Polycode Limited
3
- // config-loader.js — Parse agentic-lib.toml and resolve paths
3
+ // config-loader.js — Thin re-export from shared src/copilot/config.js
4
4
  //
5
- // TOML-only configuration. The config file is required.
6
- // All defaults are defined here in one place.
5
+ // Phase 4: Configuration logic now lives in src/copilot/config.js.
6
+ // This file re-exports for backwards compatibility with existing imports.
7
7
 
8
- import { readFileSync, existsSync } from "fs";
9
- import { dirname, join } from "path";
10
- import { parse as parseToml } from "smol-toml";
11
-
12
- /**
13
- * @typedef {Object} PathConfig
14
- * @property {string} path - The filesystem path
15
- * @property {string[]} permissions - Access permissions (e.g. ['write'])
16
- * @property {number} [limit] - Maximum number of files allowed
17
- */
18
-
19
- /**
20
- * @typedef {Object} AgenticConfig
21
- * @property {string} schedule - Schedule identifier
22
- * @property {string} supervisor - Supervisor frequency (off | weekly | daily | hourly | continuous)
23
- * @property {string} model - Copilot SDK model for LLM requests
24
- * @property {Object<string, PathConfig>} paths - Mapped paths with permissions
25
- * @property {string} testScript - Self-contained test command (e.g. "npm ci && npm test")
26
- * @property {number} featureDevelopmentIssuesWipLimit - Max concurrent feature issues
27
- * @property {number} maintenanceIssuesWipLimit - Max concurrent maintenance issues
28
- * @property {number} attemptsPerBranch - Max attempts per branch
29
- * @property {number} attemptsPerIssue - Max attempts per issue
30
- * @property {Object} seeding - Seed file configuration
31
- * @property {Object} intentionBot - Bot configuration
32
- * @property {boolean} tdd - Whether TDD mode is enabled
33
- * @property {string[]} writablePaths - All paths with write permission
34
- * @property {string[]} readOnlyPaths - All paths without write permission
35
- */
36
-
37
- // Keys whose paths are writable by agents
38
- const WRITABLE_KEYS = ["source", "tests", "behaviour", "features", "dependencies", "docs", "readme", "examples", "web"];
39
-
40
- // Default paths — every key that task handlers might access
41
- const PATH_DEFAULTS = {
42
- mission: "MISSION.md",
43
- source: "src/lib/",
44
- tests: "tests/unit/",
45
- behaviour: "tests/behaviour/",
46
- features: "features/",
47
- docs: "docs/",
48
- examples: "examples/",
49
- readme: "README.md",
50
- dependencies: "package.json",
51
- library: "library/",
52
- librarySources: "SOURCES.md",
53
- contributing: "CONTRIBUTING.md",
54
- web: "src/web/",
55
- };
56
-
57
- // Default limits for path-specific constraints
58
- const LIMIT_DEFAULTS = {
59
- features: 4,
60
- library: 32,
61
- };
62
-
63
- // Fallback profile defaults — used only when [profiles.*] is missing from TOML.
64
- // The canonical source of truth is the [profiles.*] sections in agentic-lib.toml.
65
- const FALLBACK_TUNING = {
66
- reasoningEffort: "medium",
67
- infiniteSessions: true,
68
- transformationBudget: 32,
69
- featuresScan: 10,
70
- sourceScan: 10,
71
- sourceContent: 5000,
72
- testContent: 3000,
73
- issuesScan: 20,
74
- issueBodyLimit: 500,
75
- staleDays: 30,
76
- documentSummary: 2000,
77
- discussionComments: 10,
78
- };
79
-
80
- const FALLBACK_LIMITS = {
81
- featureIssues: 2,
82
- maintenanceIssues: 1,
83
- attemptsPerBranch: 3,
84
- attemptsPerIssue: 2,
85
- featuresLimit: 4,
86
- libraryLimit: 32,
87
- };
88
-
89
- /**
90
- * Parse a TOML profile section into tuning defaults (camelCase keys).
91
- */
92
- function parseTuningProfile(profileSection) {
93
- if (!profileSection) return null;
94
- return {
95
- reasoningEffort: profileSection["reasoning-effort"] || "medium",
96
- infiniteSessions: profileSection["infinite-sessions"] ?? true,
97
- 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
- issuesScan: profileSection["max-issues"] || 20,
103
- issueBodyLimit: profileSection["issue-body-limit"] || 500,
104
- staleDays: profileSection["stale-days"] || 30,
105
- documentSummary: profileSection["max-summary-chars"] || 2000,
106
- discussionComments: profileSection["max-discussion-comments"] || 10,
107
- };
108
- }
109
-
110
- /**
111
- * Parse a TOML profile section into limits defaults (camelCase keys).
112
- */
113
- function parseLimitsProfile(profileSection) {
114
- if (!profileSection) return null;
115
- return {
116
- featureIssues: profileSection["max-feature-issues"] || 2,
117
- maintenanceIssues: profileSection["max-maintenance-issues"] || 1,
118
- attemptsPerBranch: profileSection["max-attempts-per-branch"] || 3,
119
- attemptsPerIssue: profileSection["max-attempts-per-issue"] || 2,
120
- featuresLimit: profileSection["features-limit"] || 4,
121
- libraryLimit: profileSection["library-limit"] || 32,
122
- };
123
- }
124
-
125
- /**
126
- * Read package.json from the project root, returning empty string if not found.
127
- * @param {string} tomlPath - Path to the TOML config (used to derive project root)
128
- * @param {string} depsRelPath - Relative path to package.json (from config)
129
- * @returns {string} Raw package.json content or empty string
130
- */
131
- function readPackageJson(tomlPath, depsRelPath) {
132
- try {
133
- const projectRoot = dirname(tomlPath);
134
- const pkgPath = join(projectRoot, depsRelPath);
135
- return existsSync(pkgPath) ? readFileSync(pkgPath, "utf8") : "";
136
- } catch {
137
- return "";
138
- }
139
- }
140
-
141
- /**
142
- * Resolve tuning configuration: start from profile defaults, apply explicit overrides.
143
- * @param {Object} tuningSection - The [tuning] section from TOML
144
- * @param {Object} [profilesSection] - The [profiles] section from TOML (source of truth)
145
- */
146
- function resolveTuning(tuningSection, profilesSection) {
147
- const profileName = tuningSection.profile || "recommended";
148
- const tomlProfile = profilesSection?.[profileName];
149
- const profile = parseTuningProfile(tomlProfile) || FALLBACK_TUNING;
150
- const tuning = { ...profile, profileName };
151
-
152
- // "none" explicitly disables reasoning-effort regardless of profile
153
- if (tuningSection["reasoning-effort"]) {
154
- tuning.reasoningEffort = tuningSection["reasoning-effort"] === "none" ? "" : tuningSection["reasoning-effort"];
155
- }
156
- if (tuningSection["infinite-sessions"] === true || tuningSection["infinite-sessions"] === false) {
157
- tuning.infiniteSessions = tuningSection["infinite-sessions"];
158
- }
159
- const numericOverrides = {
160
- "transformation-budget": "transformationBudget",
161
- "max-feature-files": "featuresScan",
162
- "max-source-files": "sourceScan",
163
- "max-source-chars": "sourceContent",
164
- "max-test-chars": "testContent",
165
- "max-issues": "issuesScan",
166
- "issue-body-limit": "issueBodyLimit",
167
- "stale-days": "staleDays",
168
- "max-summary-chars": "documentSummary",
169
- "max-discussion-comments": "discussionComments",
170
- };
171
- for (const [tomlKey, jsKey] of Object.entries(numericOverrides)) {
172
- if (tuningSection[tomlKey] > 0) tuning[jsKey] = tuningSection[tomlKey];
173
- }
174
-
175
- return tuning;
176
- }
177
-
178
- /**
179
- * Resolve limits configuration: start from profile defaults, apply explicit overrides.
180
- * @param {Object} limitsSection - The [limits] section from TOML
181
- * @param {string} profileName - Active profile name
182
- * @param {Object} [profilesSection] - The [profiles] section from TOML (source of truth)
183
- */
184
- function resolveLimits(limitsSection, profileName, profilesSection) {
185
- const tomlProfile = profilesSection?.[profileName];
186
- const profile = parseLimitsProfile(tomlProfile) || FALLBACK_LIMITS;
187
- return {
188
- featureIssues: limitsSection["max-feature-issues"] || profile.featureIssues,
189
- maintenanceIssues: limitsSection["max-maintenance-issues"] || profile.maintenanceIssues,
190
- attemptsPerBranch: limitsSection["max-attempts-per-branch"] || profile.attemptsPerBranch,
191
- attemptsPerIssue: limitsSection["max-attempts-per-issue"] || profile.attemptsPerIssue,
192
- featuresLimit: limitsSection["features-limit"] || profile.featuresLimit,
193
- libraryLimit: limitsSection["library-limit"] || profile.libraryLimit,
194
- };
195
- }
196
-
197
- /**
198
- * Load configuration from agentic-lib.toml.
199
- *
200
- * If configPath ends in .toml, it is used directly.
201
- * Otherwise, the project root is derived (3 levels up from configPath)
202
- * and agentic-lib.toml is loaded from there.
203
- *
204
- * @param {string} configPath - Path to config file or YAML path (for project root derivation)
205
- * @returns {AgenticConfig} Parsed configuration object
206
- * @throws {Error} If no TOML config file is found
207
- */
208
- export function loadConfig(configPath) {
209
- let tomlPath;
210
- if (configPath.endsWith(".toml")) {
211
- tomlPath = configPath;
212
- } else {
213
- const configDir = dirname(configPath);
214
- const projectRoot = join(configDir, "..", "..", "..");
215
- tomlPath = join(projectRoot, "agentic-lib.toml");
216
- }
217
-
218
- if (!existsSync(tomlPath)) {
219
- throw new Error(`Config file not found: ${tomlPath}. Create agentic-lib.toml in the project root.`);
220
- }
221
-
222
- const rawToml = readFileSync(tomlPath, "utf8");
223
- const toml = parseToml(rawToml);
224
-
225
- // Merge TOML paths with defaults, normalising library-sources → librarySources
226
- const rawPaths = { ...toml.paths };
227
- if (rawPaths["library-sources"]) {
228
- rawPaths.librarySources = rawPaths["library-sources"];
229
- delete rawPaths["library-sources"];
230
- }
231
- const mergedPaths = { ...PATH_DEFAULTS, ...rawPaths };
232
-
233
- // Build path objects with permissions
234
- const paths = {};
235
- const writablePaths = [];
236
- const readOnlyPaths = [];
237
-
238
- for (const [key, value] of Object.entries(mergedPaths)) {
239
- const isWritable = WRITABLE_KEYS.includes(key);
240
- paths[key] = { path: value, permissions: isWritable ? ["write"] : [] };
241
- if (isWritable) {
242
- writablePaths.push(value);
243
- } else {
244
- readOnlyPaths.push(value);
245
- }
246
- }
247
-
248
- const profilesSection = toml.profiles || {};
249
- const tuning = resolveTuning(toml.tuning || {}, profilesSection);
250
- const limitsSection = toml.limits || {};
251
- const resolvedLimits = resolveLimits(limitsSection, tuning.profileName, profilesSection);
252
-
253
- // Apply resolved limits to path objects
254
- paths.features.limit = resolvedLimits.featuresLimit;
255
- paths.library.limit = resolvedLimits.libraryLimit;
256
-
257
- const execution = toml.execution || {};
258
- const bot = toml.bot || {};
259
-
260
- // Mission-complete thresholds (with safe defaults)
261
- const mc = toml["mission-complete"] || {};
262
- const missionCompleteThresholds = {
263
- minResolvedIssues: mc["min-resolved-issues"] ?? 3,
264
- minDedicatedTests: mc["min-dedicated-tests"] ?? (mc["require-dedicated-tests"] === false ? 0 : 1),
265
- maxSourceTodos: mc["max-source-todos"] ?? 0,
266
- };
267
-
268
- return {
269
- supervisor: toml.schedule?.supervisor || "daily",
270
- model: toml.tuning?.model || toml.schedule?.model || "gpt-5-mini",
271
- tuning,
272
- paths,
273
- testScript: execution.test || "npm ci && npm test",
274
- featureDevelopmentIssuesWipLimit: resolvedLimits.featureIssues,
275
- maintenanceIssuesWipLimit: resolvedLimits.maintenanceIssues,
276
- attemptsPerBranch: resolvedLimits.attemptsPerBranch,
277
- attemptsPerIssue: resolvedLimits.attemptsPerIssue,
278
- transformationBudget: tuning.transformationBudget,
279
- seeding: toml.seeding || {},
280
- intentionBot: {
281
- intentionFilepath: bot["log-file"] || "intentïon.md",
282
- },
283
- init: toml.init || null,
284
- tdd: toml.tdd === true,
285
- missionCompleteThresholds,
286
- writablePaths,
287
- readOnlyPaths,
288
- configToml: rawToml,
289
- packageJson: readPackageJson(tomlPath, mergedPaths.dependencies),
290
- };
291
- }
292
-
293
- /**
294
- * Get the writable paths from config, optionally overridden by an input string.
295
- *
296
- * @param {AgenticConfig} config - Parsed config
297
- * @param {string} [override] - Semicolon-separated override paths
298
- * @returns {string[]} Array of writable paths
299
- */
300
- export function getWritablePaths(config, override) {
301
- if (override) {
302
- return override
303
- .split(";")
304
- .map((p) => p.trim())
305
- .filter(Boolean);
306
- }
307
- return config.writablePaths;
308
- }
8
+ export { loadConfig, getWritablePaths } from "../../copilot/config.js";