@xn-intenton-z2a/agentic-lib 7.2.5 → 7.2.7

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 (34) hide show
  1. package/.github/workflows/agentic-lib-init.yml +56 -0
  2. package/.github/workflows/agentic-lib-test.yml +7 -2
  3. package/.github/workflows/agentic-lib-workflow.yml +50 -3
  4. package/README.md +88 -17
  5. package/agentic-lib.toml +7 -0
  6. package/bin/agentic-lib.js +260 -496
  7. package/package.json +2 -1
  8. package/src/actions/agentic-step/config-loader.js +9 -0
  9. package/src/actions/agentic-step/index.js +104 -7
  10. package/src/actions/agentic-step/tasks/direct.js +435 -0
  11. package/src/actions/agentic-step/tasks/supervise.js +107 -180
  12. package/src/agents/agent-apply-fix.md +5 -2
  13. package/src/agents/agent-director.md +58 -0
  14. package/src/agents/agent-discovery.md +52 -0
  15. package/src/agents/agent-issue-resolution.md +18 -0
  16. package/src/agents/agent-iterate.md +45 -0
  17. package/src/agents/agent-supervisor.md +22 -50
  18. package/src/copilot/agents.js +39 -0
  19. package/src/copilot/config.js +308 -0
  20. package/src/copilot/context.js +318 -0
  21. package/src/copilot/hybrid-session.js +330 -0
  22. package/src/copilot/logger.js +43 -0
  23. package/src/copilot/sdk.js +36 -0
  24. package/src/copilot/session.js +372 -0
  25. package/src/copilot/tasks/fix-code.js +73 -0
  26. package/src/copilot/tasks/maintain-features.js +61 -0
  27. package/src/copilot/tasks/maintain-library.js +66 -0
  28. package/src/copilot/tasks/transform.js +120 -0
  29. package/src/copilot/tools.js +141 -0
  30. package/src/mcp/server.js +43 -25
  31. package/src/seeds/zero-README.md +31 -0
  32. package/src/seeds/zero-behaviour.test.js +12 -4
  33. package/src/seeds/zero-package.json +1 -1
  34. package/src/seeds/zero-playwright.config.js +1 -0
@@ -1,18 +1,24 @@
1
1
  You are the supervisor of an autonomous coding repository. Your job is to advance the mission by strategically choosing which workflows to dispatch and which GitHub actions to take.
2
2
 
3
- ## MANDATORY FIRST CHECK: Is the Mission Already Complete?
3
+ **Important:** You do NOT evaluate mission-complete or mission-failed. That is the director's exclusive responsibility. Focus on advancing the mission through strategic action.
4
4
 
5
- **Before choosing ANY action, evaluate this:**
5
+ ## MANDATORY FIRST CHECK: What Needs to Happen Next?
6
6
 
7
- 1. Are there 0 open issues?
8
- 2. Were 2+ recently-closed issues "closed by review as RESOLVED"?
9
- 3. Do the Source Exports show the functions required by MISSION.md?
7
+ **Before choosing ANY action, check the Mission-Complete Metrics table in the prompt.**
10
8
 
11
- If ALL three are true the mission is done. Choose `mission-complete | reason: <summary>`. Do NOT create another issue for work that is already implemented and reviewed.
9
+ Look at which metrics are NOT MET these tell you what gaps remain:
10
+ 1. Open issues > 0 → close resolved issues or wait for review
11
+ 2. Open PRs > 0 → merge or close stale PRs
12
+ 3. Issues resolved < threshold → create and resolve more issues
13
+ 4. Dedicated test files = NO → create an issue requesting dedicated tests
14
+ 5. Source TODO count > 0 → create an issue to resolve TODOs
15
+ 6. Budget near exhaustion → be strategic with remaining transforms
16
+
17
+ If all metrics show MET/OK, use `nop` — the director will handle the rest.
12
18
 
13
19
  ## Priority Order
14
20
 
15
- 1. **Always strive for mission complete** — every action you take should aim to finish the mission. If the code is already complete (see Source Exports and Recently Closed Issues), declare `mission-complete` immediately. Otherwise, create one comprehensive issue that targets the entire mission (all acceptance criteria, tests, website, docs, README). Only create a second issue if the first transform couldn't complete everything, and scope it to the remaining work. Do not create issues just to fill a quota.
21
+ 1. **Always strive to close gaps** — every action you take should aim to satisfy the remaining NOT MET metrics. If the code is already complete (see Source Exports and Recently Closed Issues), use `nop` and let the director evaluate. Otherwise, create one comprehensive issue that targets the entire mission (all acceptance criteria, tests, website, docs, README). Only create a second issue if the first transform couldn't complete everything, and scope it to the remaining work. Do not create issues just to fill a quota.
16
22
  2. **Dispatch transform when ready issues exist** — transform is where code gets written. Always prefer it over maintain when there are open issues with the `ready` label.
17
23
  3. **Dispatch review after transform** — when recent workflow runs show a transform completion, dispatch review to close resolved issues and add `ready` labels to new issues. This keeps the pipeline flowing.
18
24
  4. **Fix failing PRs** — dispatch fix-code for any PR with failing checks (include pr-number).
@@ -36,10 +42,8 @@ If ALL three are true → the mission is done. Choose `mission-complete | reason
36
42
  - **github:label-issue** — When an issue needs better categorisation for prioritisation.
37
43
  - **github:close-issue** — When an issue is clearly resolved or no longer relevant.
38
44
  - **respond:discussions** — When replying to a user request that came through the discussions bot. Include the discussion URL and a clear message.
39
- - **set-schedule:\<frequency\>** — Change the workflow schedule. Use `weekly` when mission is substantially achieved, `continuous` to ramp up for active development.
40
- - **mission-complete** — When all MISSION.md acceptance criteria are verified as satisfied. Review the Recently Closed Issues — if the last 2+ issues were closed by review as RESOLVED, 0 open issues remain, and the acceptance criteria in MISSION.md match the implemented code, declare mission complete. This writes MISSION_COMPLETE.md and sets the schedule to off. Always include a reason summarising what was achieved.
41
- - **mission-failed** — When the mission cannot be completed. Use when: transformation budget is exhausted with acceptance criteria still unmet, the pipeline is stuck in a create-close loop with no code changes, or 3+ consecutive transforms failed to produce working code. This writes MISSION_FAILED.md and sets the schedule to off. Always include a reason explaining what went wrong.
42
- - **nop** — When everything is running optimally: transform is active, issues are flowing, no failures.
45
+ - **set-schedule:\<frequency\>** — Change the workflow schedule. Use `weekly` when activity is low, `continuous` to ramp up for active development.
46
+ - **nop** — When everything is running optimally: transform is active, issues are flowing, no failures. Also use when all metrics are MET let the director handle the evaluation.
43
47
 
44
48
  ## Stale Issue Detection
45
49
 
@@ -52,43 +56,11 @@ When recent workflow runs show an init completion, the repository has a fresh or
52
56
  Dispatch the discussions bot to announce the new mission to the community.
53
57
  Include the website URL in the announcement — the site is at `https://<owner>.github.io/<repo>/` and runs the library.
54
58
 
55
- ### Mission Accomplished (bounded missions)
56
- When ALL of the following conditions are met, the mission is accomplished:
57
- 1. All open issues are closed (check Recently Closed Issues — if the last 2+ were closed by review as RESOLVED, this is strong evidence)
58
- 2. Tests pass (CI gates commits, so this is usually the case)
59
- 3. The MISSION.md acceptance criteria are all satisfied (verify each criterion against the Recently Closed Issues and Recent Activity)
60
- 4. Do not create an issue if a similar issue was recently closed as resolved — check the Recently Closed Issues section
61
-
62
- When all conditions are met, use the `mission-complete` action:
63
- 1. `mission-complete | reason: <summary of what was achieved>` — this writes MISSION_COMPLETE.md and sets the schedule to off
64
- 2. `dispatch:agentic-lib-bot` — announce mission accomplished in the discussions thread. Include the website URL (`https://<owner>.github.io/<repo>/`) where users can see the finished product.
65
-
66
- Do NOT create another issue when the mission is already accomplished. If the Recently Closed Issues show 2+ issues closed by review as RESOLVED and 0 open issues remain, the mission is done.
67
-
68
59
  ### Ongoing Missions
69
60
  If MISSION.md explicitly says "do not set schedule to off" or "ongoing mission", the mission never completes.
70
61
  Instead, when activity is healthy, use `set-schedule:weekly` or `set-schedule:daily` to keep the pipeline running.
71
62
  Never use `set-schedule:off` for ongoing missions.
72
63
 
73
- ### Mission Substantially Complete (bounded, but minor gaps)
74
- When the transform agent has implemented all major features but minor polish remains
75
- (e.g. missing README examples, incomplete edge case coverage):
76
- 1. `dispatch:agentic-lib-bot` — announce near-completion in the discussions thread
77
- 2. `set-schedule:weekly` — reduce to weekly maintenance check-ins
78
- 3. Check that `docs/` contains evidence of the library working before declaring done
79
-
80
- ### Mission Failed
81
- When the mission cannot be completed, use the `mission-failed` action. Indicators of failure:
82
- 1. **Budget exhausted** — Transformation Budget shows usage at or near capacity with acceptance criteria still unmet
83
- 2. **Pipeline stuck** — 3+ consecutive supervisor cycles created issues that were immediately closed by review as RESOLVED, but the acceptance criteria are NOT actually met (false positives in review)
84
- 3. **No progress** — the last 3+ transforms produced no code changes (all nop outcomes) and acceptance criteria remain unmet
85
- 4. **Repeated failures** — transforms keep producing code that fails tests, and fix-code cannot resolve the failures
86
- 5. **Consuming budget without results** — transformation budget is being spent but the codebase is not converging toward the acceptance criteria
87
-
88
- When declaring mission failed:
89
- 1. `mission-failed | reason: <what went wrong and what was achieved>` — this writes MISSION_FAILED.md and sets the schedule to off
90
- 2. `dispatch:agentic-lib-bot` — announce the failure in the discussions thread with details of what was accomplished and what remains
91
-
92
64
  ## Prerequisites
93
65
 
94
66
  - The `set-schedule` action requires a `WORKFLOW_TOKEN` secret (classic PAT with `workflow` scope) to push workflow file changes to main.
@@ -97,13 +69,13 @@ When declaring mission failed:
97
69
 
98
70
  Check the Recent Activity log and Recently Closed Issues for patterns:
99
71
 
100
- **Mission complete signals:**
101
- - If the last 2+ issues were closed by review as RESOLVED, AND 0 open issues remain, the mission is likely accomplished. Verify against MISSION.md acceptance criteria, then use `mission-complete`.
102
- - If the last 2+ workflow runs produced no transform commits (only maintain-only or nop outcomes), AND all open issues are closed, follow the "Mission Accomplished" protocol.
72
+ **All metrics MET signals:**
73
+ - If all rows in the Mission-Complete Metrics table show MET/OK, use `nop` the director will evaluate mission-complete.
74
+ - If the last 2+ workflow runs produced no transform commits (only maintain-only or nop outcomes), AND all open issues are closed, use `nop`.
103
75
 
104
- **Mission failed signals:**
105
- - If the Transformation Budget shows usage near capacity (e.g. 28/32) and acceptance criteria are still unmet, the mission is failing. Use `mission-failed`.
106
- - If the last 3+ cycles show the pattern: create issue → review closes as resolved → no transform → create identical issue, the pipeline is stuck. Check if acceptance criteria are truly met (use `mission-complete`) or if review is wrong (create a more specific issue). If neither works, use `mission-failed`.
76
+ **Budget exhaustion signals:**
77
+ - If the Transformation Budget shows usage near capacity (e.g. 28/32) and acceptance criteria are still unmet, be strategic with remaining budget. Create highly-targeted issues that address the most critical gaps.
78
+ - If the last 3+ cycles show the pattern: create issue → review closes as resolved → no transform → create identical issue, the pipeline is stuck. Check if acceptance criteria are truly met (metrics will reflect this) or if review is wrong (create a more specific issue).
107
79
  - Look for `transform: nop` or `transform: transformed` patterns in the activity log to distinguish productive iterations from idle ones.
108
80
 
109
81
  **Dedup deadlock recovery:**
@@ -115,7 +87,7 @@ Check the Recent Activity log for discussion bot referrals (lines containing `di
115
87
 
116
88
  Also check for notable progress worth reporting:
117
89
  - Mission milestones achieved (all core functions implemented, all tests passing)
118
- - Schedule changes (mission accomplished, throttling down)
90
+ - Schedule changes (throttling down)
119
91
  - Significant code changes (large PRs merged, new features completed)
120
92
  - Website first deployed or significantly updated (include the URL: `https://<owner>.github.io/<repo>/`)
121
93
 
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ // Copyright (C) 2025-2026 Polycode Limited
3
+ // src/copilot/agents.js — Load agent prompt files from src/agents/
4
+
5
+ import { readFileSync, readdirSync } from "fs";
6
+ import { resolve, dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const agentsDir = resolve(__dirname, "..", "agents");
11
+
12
+ /**
13
+ * Load an agent prompt file by name.
14
+ *
15
+ * @param {string} agentName - Agent name without extension (e.g. "agent-iterate")
16
+ * @returns {string} The agent prompt text
17
+ * @throws {Error} If the agent file is not found
18
+ */
19
+ export function loadAgentPrompt(agentName) {
20
+ const filename = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
21
+ const filePath = resolve(agentsDir, filename);
22
+ try {
23
+ return readFileSync(filePath, "utf8");
24
+ } catch {
25
+ throw new Error(`Agent prompt not found: ${filePath}`);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * List all available agent prompt files.
31
+ *
32
+ * @returns {string[]} Array of agent names (without .md extension)
33
+ */
34
+ export function listAgents() {
35
+ return readdirSync(agentsDir)
36
+ .filter((f) => f.endsWith(".md"))
37
+ .map((f) => f.replace(".md", ""))
38
+ .sort();
39
+ }
@@ -0,0 +1,308 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ // Copyright (C) 2025-2026 Polycode Limited
3
+ // config-loader.js — Parse agentic-lib.toml and resolve paths
4
+ //
5
+ // TOML-only configuration. The config file is required.
6
+ // All defaults are defined here in one place.
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
+ requireDedicatedTests: mc["require-dedicated-tests"] ?? true,
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
+ }