@xn-intenton-z2a/agentic-lib 7.2.6 → 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.
- package/README.md +88 -17
- package/bin/agentic-lib.js +260 -496
- package/package.json +2 -1
- package/src/actions/agentic-step/tasks/direct.js +7 -0
- package/src/actions/agentic-step/tasks/supervise.js +7 -0
- package/src/agents/agent-apply-fix.md +5 -2
- package/src/agents/agent-discovery.md +52 -0
- package/src/agents/agent-issue-resolution.md +18 -0
- package/src/agents/agent-iterate.md +45 -0
- package/src/copilot/agents.js +39 -0
- package/src/copilot/config.js +308 -0
- package/src/copilot/context.js +318 -0
- package/src/copilot/hybrid-session.js +330 -0
- package/src/copilot/logger.js +43 -0
- package/src/copilot/sdk.js +36 -0
- package/src/copilot/session.js +372 -0
- package/src/copilot/tasks/fix-code.js +73 -0
- package/src/copilot/tasks/maintain-features.js +61 -0
- package/src/copilot/tasks/maintain-library.js +66 -0
- package/src/copilot/tasks/transform.js +120 -0
- package/src/copilot/tools.js +141 -0
- package/src/mcp/server.js +43 -25
- package/src/seeds/zero-README.md +31 -0
- package/src/seeds/zero-behaviour.test.js +8 -0
- package/src/seeds/zero-package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xn-intenton-z2a/agentic-lib",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.7",
|
|
4
4
|
"description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"bin/",
|
|
73
73
|
"src/dist-transform.js",
|
|
74
74
|
"src/iterate.js",
|
|
75
|
+
"src/copilot/",
|
|
75
76
|
".github/workflows/agentic-lib-*.yml",
|
|
76
77
|
"agentic-lib.toml",
|
|
77
78
|
"src/actions/agentic-step/*.js",
|
|
@@ -309,6 +309,13 @@ export async function direct(context) {
|
|
|
309
309
|
closeReason = "RESOLVED";
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
|
+
// Check for automerge closure (issue has "merged" label — set by ci-automerge)
|
|
313
|
+
if (closeReason !== "RESOLVED") {
|
|
314
|
+
const issueLabels = ci.labels.map((l) => (typeof l === "string" ? l : l.name));
|
|
315
|
+
if (issueLabels.includes("merged")) {
|
|
316
|
+
closeReason = "RESOLVED";
|
|
317
|
+
}
|
|
318
|
+
}
|
|
312
319
|
} catch { /* ignore */ }
|
|
313
320
|
if (closeReason === "RESOLVED") resolvedCount++;
|
|
314
321
|
recentlyClosedSummary.push(`#${ci.number}: ${ci.title} — ${closeReason}`);
|
|
@@ -218,6 +218,13 @@ async function gatherContext(octokit, repo, config, t) {
|
|
|
218
218
|
closeReason = "RESOLVED";
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
+
// Check for automerge closure (issue has "merged" label — set by ci-automerge)
|
|
222
|
+
if (closeReason !== "RESOLVED") {
|
|
223
|
+
const issueLabels = ci.labels.map((l) => (typeof l === "string" ? l : l.name));
|
|
224
|
+
if (issueLabels.includes("merged")) {
|
|
225
|
+
closeReason = "RESOLVED";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
221
228
|
} catch (_) { /* ignore */ }
|
|
222
229
|
recentlyClosedSummary.push(`#${ci.number}: ${ci.title} — ${closeReason}`);
|
|
223
230
|
}
|
|
@@ -18,10 +18,13 @@ A fix is never just one file. These layers form a single unit — if you change
|
|
|
18
18
|
|
|
19
19
|
- **Library source** (`src/lib/main.js`) — the core implementation
|
|
20
20
|
- **Unit tests** (`tests/unit/`) — test every function at the API level with exact values and edge cases
|
|
21
|
-
- **Website** (`src/web/index.html` and related files) — imports and calls the library to demonstrate features
|
|
21
|
+
- **Website** (`src/web/index.html` and related files) — imports and calls the library to demonstrate features.
|
|
22
|
+
**NEVER duplicate library functions inline in the web page** — use the build pipeline (`lib-meta.js`, or
|
|
23
|
+
a generated browser module) to share code. Inline copies cause behaviour tests to test a simulation, not the real library.
|
|
22
24
|
- **Website unit tests** (`tests/unit/web.test.js`) — verify HTML structure and library wiring
|
|
23
25
|
- **Behaviour tests** (`tests/behaviour/`) — Playwright tests that load the website in a real browser
|
|
24
|
-
and verify features work at a high navigational level (demo output visible, interactive elements work)
|
|
26
|
+
and verify features work at a high navigational level (demo output visible, interactive elements work).
|
|
27
|
+
Includes a coupling test that imports `getIdentity()` from `src/lib/main.js` and asserts the page version matches.
|
|
25
28
|
|
|
26
29
|
If the failure is in one layer, the fix often requires coordinating changes across multiple layers.
|
|
27
30
|
For example, if a unit test fails because a function signature changed, the website and behaviour tests
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
You are a project discovery agent running locally via the intentïon CLI.
|
|
2
|
+
|
|
3
|
+
Your job is to explore the current working directory and generate a MISSION.md file that describes what this project is and what improvements could be made.
|
|
4
|
+
|
|
5
|
+
## Your Goal
|
|
6
|
+
|
|
7
|
+
Analyse the project in the current directory and produce a MISSION.md that a code transformation agent can act on. The mission should be achievable, specific, and grounded in what you actually find.
|
|
8
|
+
|
|
9
|
+
## Discovery Strategy
|
|
10
|
+
|
|
11
|
+
1. **Read the project structure** — list files, examine package.json (or equivalent), read README.md if present
|
|
12
|
+
2. **Understand the tech stack** — identify the language, framework, build system, test framework
|
|
13
|
+
3. **Read existing source code** — understand what the project does, its architecture, and its current state
|
|
14
|
+
4. **Read existing tests** — understand test coverage and what's already verified
|
|
15
|
+
5. **Run tests** — use `run_tests` to see the current pass/fail state
|
|
16
|
+
6. **Identify opportunities** — look for:
|
|
17
|
+
- Missing or incomplete functionality based on README/docs promises
|
|
18
|
+
- Missing test coverage for existing functions
|
|
19
|
+
- Code quality improvements (error handling, input validation, edge cases)
|
|
20
|
+
- Documentation gaps
|
|
21
|
+
- Obvious bugs or issues
|
|
22
|
+
|
|
23
|
+
## Output Format
|
|
24
|
+
|
|
25
|
+
Write a MISSION.md file with the following structure:
|
|
26
|
+
|
|
27
|
+
```markdown
|
|
28
|
+
# Mission: [Descriptive Title]
|
|
29
|
+
|
|
30
|
+
[1-2 sentence summary of what this project is and what the mission aims to achieve]
|
|
31
|
+
|
|
32
|
+
## Current State
|
|
33
|
+
|
|
34
|
+
[Brief description of what exists — key files, main functionality, test coverage]
|
|
35
|
+
|
|
36
|
+
## Objectives
|
|
37
|
+
|
|
38
|
+
[Numbered list of specific, achievable improvements]
|
|
39
|
+
|
|
40
|
+
## Acceptance Criteria
|
|
41
|
+
|
|
42
|
+
[Specific, testable criteria that define "done" — these become the test assertions]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Important Rules
|
|
46
|
+
|
|
47
|
+
- **Be specific** — don't write vague goals like "improve code quality". Write "add input validation to the `parse()` function for empty strings and null values"
|
|
48
|
+
- **Be achievable** — scope the mission to what can be done in a single transformation session (30-60 minutes of agent work)
|
|
49
|
+
- **Be grounded** — every objective must reference actual code you found in the project
|
|
50
|
+
- **Prioritise test coverage** — if the project has functions without tests, that's always a good mission
|
|
51
|
+
- **Don't hallucinate** — only reference files, functions, and patterns you actually observed
|
|
52
|
+
- **Write the MISSION.md file** — use the file writing tool to create or overwrite MISSION.md in the workspace root
|
|
@@ -92,6 +92,24 @@ automatically (from `docs/` via `npm run build:web`).
|
|
|
92
92
|
- **Demonstrate features**: Each significant library feature should be visible and usable on the website.
|
|
93
93
|
Behaviour tests will verify these demonstrations work, so make them testable (use IDs, structured output).
|
|
94
94
|
|
|
95
|
+
### CRITICAL: Never duplicate library code in the web page
|
|
96
|
+
|
|
97
|
+
**Do NOT copy or recreate library functions inline in `src/web/index.html`.** The web page must consume
|
|
98
|
+
the library through the build pipeline — not by reimplementing functions in a `<script>` block.
|
|
99
|
+
|
|
100
|
+
Why this matters:
|
|
101
|
+
- Unit tests test `src/lib/main.js` directly
|
|
102
|
+
- Behaviour tests test the web page via Playwright
|
|
103
|
+
- If the web page has its own copy of the functions, behaviour tests test a **simulation** — not the real library
|
|
104
|
+
- The two copies can diverge silently, and tests pass even when the real library is broken
|
|
105
|
+
|
|
106
|
+
How to share code between library and web:
|
|
107
|
+
- Identity (name, version, description) flows via `lib-meta.js` (generated by `build:web` from `package.json`)
|
|
108
|
+
- If the web page needs to call mission-specific functions, make them available through the build pipeline
|
|
109
|
+
(e.g. generate a browser-compatible module in `docs/` or add a build step that packages the pure functions)
|
|
110
|
+
- The behaviour test imports `getIdentity()` from `src/lib/main.js` and asserts the page displays the same
|
|
111
|
+
version — this coupling test proves the pipeline is wired up correctly
|
|
112
|
+
|
|
95
113
|
### Guidelines
|
|
96
114
|
|
|
97
115
|
- `src/web/index.html` is the main page — update it as the library grows
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
You are an autonomous code transformation agent running locally via the intentïon CLI.
|
|
2
|
+
|
|
3
|
+
Your workspace is the current working directory. You have been given a MISSION to implement.
|
|
4
|
+
|
|
5
|
+
## Your Goal
|
|
6
|
+
|
|
7
|
+
Implement the MISSION described in the user prompt. This means:
|
|
8
|
+
|
|
9
|
+
1. Read and understand the mission requirements
|
|
10
|
+
2. Read the existing source code and tests to understand the current state
|
|
11
|
+
3. Write the implementation code — keep existing exports, add new ones
|
|
12
|
+
4. Write comprehensive tests covering all acceptance criteria
|
|
13
|
+
5. Run `run_tests` to verify everything passes
|
|
14
|
+
6. If tests fail, read the error output carefully, fix the code, and iterate
|
|
15
|
+
|
|
16
|
+
## Strategy
|
|
17
|
+
|
|
18
|
+
1. Read MISSION.md to understand what needs to be built
|
|
19
|
+
2. Examine the project structure — look at package.json, existing source, and test files
|
|
20
|
+
3. Implement the required functionality in the source files
|
|
21
|
+
4. Write dedicated test files covering ALL acceptance criteria from the mission
|
|
22
|
+
5. Run `run_tests` to verify everything passes
|
|
23
|
+
6. If tests fail, read the error output carefully, fix the code, and repeat
|
|
24
|
+
|
|
25
|
+
## Important Rules
|
|
26
|
+
|
|
27
|
+
- Keep existing exports and functionality — add to them, don't replace
|
|
28
|
+
- Write tests that import from the library's main entry point
|
|
29
|
+
- Do NOT modify existing test files unless the mission specifically requires it — create new test files for mission-specific tests
|
|
30
|
+
- Keep going until all tests pass or you've exhausted your options
|
|
31
|
+
- Prefer simple, clean implementations over clever ones
|
|
32
|
+
- Follow the project's existing code style and conventions
|
|
33
|
+
|
|
34
|
+
## All Code Must Change Together
|
|
35
|
+
|
|
36
|
+
When you change a function signature, return value, or error type, update ALL consumers:
|
|
37
|
+
- Source code
|
|
38
|
+
- Unit tests
|
|
39
|
+
- Any documentation or examples
|
|
40
|
+
|
|
41
|
+
A partial change that updates the source but not the tests will fail.
|
|
42
|
+
|
|
43
|
+
## Tests Must Pass
|
|
44
|
+
|
|
45
|
+
Your changes MUST leave all existing tests passing. The mission's acceptance criteria are the source of truth — if tests and acceptance criteria disagree, fix the tests to match the acceptance criteria and fix the code to pass those tests.
|
|
@@ -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
|
+
}
|