@xn-intenton-z2a/agentic-lib 7.4.13 → 7.4.15
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/.github/agents/agent-apply-fix.md +30 -1
- package/.github/agents/agent-director.md +28 -7
- package/.github/agents/agent-discussion-bot.md +28 -0
- package/.github/agents/agent-implementation-review.md +21 -0
- package/.github/agents/agent-issue-resolution.md +32 -0
- package/.github/agents/agent-iterate.md +33 -0
- package/.github/agents/agent-maintain-features.md +34 -0
- package/.github/agents/agent-maintain-library.md +39 -0
- package/.github/agents/agent-ready-issue.md +21 -0
- package/.github/agents/agent-review-issue.md +16 -0
- package/.github/agents/agent-supervisor.md +60 -0
- package/.github/workflows/agentic-lib-init.yml +76 -11
- package/.github/workflows/agentic-lib-schedule.yml +58 -6
- package/.github/workflows/agentic-lib-test.yml +31 -3
- package/.github/workflows/agentic-lib-update.yml +20 -0
- package/.github/workflows/agentic-lib-workflow.yml +63 -52
- package/README.md +23 -12
- package/agentic-lib.toml +3 -3
- package/bin/agentic-lib.js +34 -4
- package/package.json +1 -1
- package/src/actions/agentic-step/index.js +51 -34
- package/src/actions/agentic-step/logging.js +7 -14
- package/src/actions/agentic-step/tasks/direct.js +52 -11
- package/src/actions/agentic-step/tasks/maintain-features.js +7 -0
- package/src/actions/agentic-step/tasks/maintain-library.js +10 -0
- package/src/actions/agentic-step/tasks/supervise.js +14 -6
- package/src/actions/agentic-step/tasks/transform.js +37 -1
- package/src/actions/commit-if-changed/action.yml +2 -1
- package/src/copilot/config.js +3 -3
- package/src/copilot/guards.js +5 -5
- package/src/copilot/state.js +211 -0
- package/src/copilot/telemetry.js +88 -10
- package/src/seeds/missions/1-dan-create-c64-emulator.md +13 -13
- package/src/seeds/missions/1-dan-create-planning-engine.md +82 -0
- package/src/seeds/missions/1-kyu-create-ray-tracer.md +31 -8
- package/src/seeds/missions/2-dan-create-self-hosted.md +67 -0
- package/src/seeds/missions/2-kyu-create-markdown-compiler.md +48 -0
- package/src/seeds/missions/2-kyu-create-plot-code-lib.md +35 -16
- package/src/seeds/missions/3-kyu-analyze-lunar-lander.md +13 -14
- package/src/seeds/missions/3-kyu-evaluate-time-series-lab.md +22 -28
- package/src/seeds/missions/4-kyu-analyze-json-schema-diff.md +46 -2
- package/src/seeds/missions/4-kyu-apply-cron-engine.md +16 -18
- package/src/seeds/missions/4-kyu-apply-dense-encoding.md +14 -11
- package/src/seeds/missions/4-kyu-apply-owl-ontology.md +47 -0
- package/src/seeds/missions/5-kyu-apply-ascii-face.md +40 -0
- package/src/seeds/missions/5-kyu-apply-string-utils.md +17 -17
- package/src/seeds/missions/6-kyu-understand-hamming-distance.md +12 -12
- package/src/seeds/missions/6-kyu-understand-roman-numerals.md +12 -12
- package/src/seeds/missions/8-kyu-remember-hello-world.md +10 -0
- package/src/seeds/zero-MISSION.md +12 -12
- package/src/seeds/zero-package.json +1 -1
- package/src/seeds/missions/2-dan-create-agi.md +0 -22
- package/src/seeds/missions/2-kyu-evaluate-markdown-compiler.md +0 -33
- package/src/seeds/missions/3-kyu-evaluate-owl-ontology.md +0 -34
- package/src/seeds/missions/5-kyu-create-ascii-face.md +0 -4
|
@@ -8,16 +8,17 @@
|
|
|
8
8
|
import * as core from "@actions/core";
|
|
9
9
|
import * as github from "@actions/github";
|
|
10
10
|
import { loadConfig, getWritablePaths } from "./config-loader.js";
|
|
11
|
-
import {
|
|
12
|
-
import { readFileSync, existsSync } from "fs";
|
|
11
|
+
import { generateClosingNotes, writeAgentLog } from "./logging.js";
|
|
12
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
13
13
|
import {
|
|
14
14
|
buildMissionMetrics, buildMissionReadiness,
|
|
15
|
-
computeTransformationCost,
|
|
15
|
+
computeTransformationCost, buildLimitsStatus,
|
|
16
16
|
} from "../../copilot/telemetry.js";
|
|
17
17
|
import {
|
|
18
18
|
checkInstabilityLabel, countDedicatedTests,
|
|
19
19
|
countOpenIssues, countResolvedIssues, countMdFiles,
|
|
20
20
|
} from "./metrics.js";
|
|
21
|
+
import { readState, writeState, updateStateAfterTask } from "../../copilot/state.js";
|
|
21
22
|
|
|
22
23
|
// Task implementations
|
|
23
24
|
import { resolveIssue } from "./tasks/resolve-issue.js";
|
|
@@ -63,9 +64,22 @@ async function run() {
|
|
|
63
64
|
if (!handler) throw new Error(`Unknown task: ${task}. Available: ${Object.keys(TASKS).join(", ")}`);
|
|
64
65
|
|
|
65
66
|
// Resolve log and screenshot paths (fetched from agentic-lib-logs branch by workflow)
|
|
66
|
-
const
|
|
67
|
+
const logPrefix = config.intentionBot?.logPrefix || "agent-log-";
|
|
67
68
|
const screenshotFile = config.intentionBot?.screenshotFile || "SCREENSHOT_INDEX.png";
|
|
68
|
-
|
|
69
|
+
// Find the most recent agent-log file matching the prefix for LLM context
|
|
70
|
+
const logDir = logPrefix.includes("/") ? logPrefix.substring(0, logPrefix.lastIndexOf("/")) : ".";
|
|
71
|
+
const logBase = logPrefix.includes("/") ? logPrefix.substring(logPrefix.lastIndexOf("/") + 1) : logPrefix;
|
|
72
|
+
let logFilePath = null;
|
|
73
|
+
try {
|
|
74
|
+
const logFiles = readdirSync(logDir)
|
|
75
|
+
.filter(f => f.startsWith(logBase) && f.endsWith(".md"))
|
|
76
|
+
.sort();
|
|
77
|
+
if (logFiles.length > 0) {
|
|
78
|
+
const newest = logFiles[logFiles.length - 1];
|
|
79
|
+
const candidate = logDir === "." ? newest : `${logDir}/${newest}`;
|
|
80
|
+
if (existsSync(candidate)) logFilePath = candidate;
|
|
81
|
+
}
|
|
82
|
+
} catch { /* no log files yet */ }
|
|
69
83
|
const screenshotFilePath = existsSync(screenshotFile) ? screenshotFile : null;
|
|
70
84
|
|
|
71
85
|
const context = {
|
|
@@ -79,6 +93,9 @@ async function run() {
|
|
|
79
93
|
logFilePath, screenshotFilePath,
|
|
80
94
|
};
|
|
81
95
|
|
|
96
|
+
// C1: Read persistent state from agentic-lib-state.toml
|
|
97
|
+
const state = readState(".");
|
|
98
|
+
|
|
82
99
|
const startTime = Date.now();
|
|
83
100
|
const result = await handler(context);
|
|
84
101
|
const durationMs = Date.now() - startTime;
|
|
@@ -96,15 +113,17 @@ async function run() {
|
|
|
96
113
|
&& await checkInstabilityLabel(context, issueNumber);
|
|
97
114
|
if (isInstability) core.info(`Issue #${issueNumber} has instability label — does not count against budget`);
|
|
98
115
|
const transformationCost = computeTransformationCost(task, result.outcome, isInstability);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
|
|
117
|
+
// C1/C2: Update persistent state with this task's results
|
|
118
|
+
updateStateAfterTask(state, {
|
|
119
|
+
task,
|
|
120
|
+
outcome: result.outcome || "completed",
|
|
121
|
+
transformationCost,
|
|
122
|
+
tokensUsed: result.tokensUsed || 0,
|
|
123
|
+
});
|
|
124
|
+
// C2: Use cumulative cost from persistent state (not just this task)
|
|
125
|
+
const cumulativeCost = state.counters["cumulative-transforms"] || 0;
|
|
126
|
+
state.budget["transformation-budget-cap"] = config.transformationBudget || 0;
|
|
108
127
|
|
|
109
128
|
const { featureIssueCount, maintenanceIssueCount } = await countOpenIssues(context);
|
|
110
129
|
if (result.resolvedCount == null) result.resolvedCount = await countResolvedIssues(context);
|
|
@@ -121,39 +140,37 @@ async function run() {
|
|
|
121
140
|
}
|
|
122
141
|
}
|
|
123
142
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
outputTokens: result.outputTokens, cost: result.cost, durationMs,
|
|
132
|
-
model: result.model || model, details: result.details,
|
|
133
|
-
workflowUrl: `${process.env.GITHUB_SERVER_URL}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}`,
|
|
134
|
-
profile: config.tuning?.profileName || "unknown",
|
|
135
|
-
changes: result.changes, contextNotes: result.contextNotes,
|
|
136
|
-
limitsStatus, promptBudget: result.promptBudget,
|
|
137
|
-
missionReadiness: buildMissionReadiness(missionMetrics),
|
|
138
|
-
missionMetrics, closingNotes: result.closingNotes || generateClosingNotes(limitsStatus),
|
|
139
|
-
transformationCost, narrative: result.narrative,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
143
|
+
// C5: Pass per-task costs for split display
|
|
144
|
+
const taskCosts = {
|
|
145
|
+
transformationCost,
|
|
146
|
+
tokensUsed: result.tokensUsed || 0,
|
|
147
|
+
cumulativeTokens: state.counters["total-tokens"] || 0,
|
|
148
|
+
};
|
|
149
|
+
const missionMetrics = buildMissionMetrics(config, result, limitsStatus, cumulativeCost, featureIssueCount, maintenanceIssueCount, taskCosts);
|
|
142
150
|
|
|
143
|
-
// Write standalone agent log file
|
|
151
|
+
// C4: Write standalone agent log file with sequence number
|
|
152
|
+
const seq = state.counters["log-sequence"] || 0;
|
|
144
153
|
try {
|
|
145
154
|
const agentLogFile = writeAgentLog({
|
|
146
155
|
task, outcome: result.outcome || "completed",
|
|
147
156
|
model: result.model || model, durationMs, tokensUsed: result.tokensUsed,
|
|
148
157
|
narrative: result.narrative, contextNotes: result.contextNotes,
|
|
149
158
|
reviewTable: result.reviewTable, completenessAdvice: result.completenessAdvice,
|
|
150
|
-
missionMetrics,
|
|
159
|
+
missionMetrics, sequence: seq,
|
|
151
160
|
});
|
|
152
161
|
core.info(`Agent log written: ${agentLogFile}`);
|
|
153
162
|
} catch (err) {
|
|
154
163
|
core.warning(`Could not write agent log: ${err.message}`);
|
|
155
164
|
}
|
|
156
165
|
|
|
166
|
+
// C1: Write updated state back to agentic-lib-state.toml
|
|
167
|
+
try {
|
|
168
|
+
writeState(".", state);
|
|
169
|
+
core.info(`State written: seq=${seq}, transforms=${cumulativeCost}, nops=${state.counters["cumulative-nop-cycles"]}`);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
core.warning(`Could not write state: ${err.message}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
157
174
|
core.info(`agentic-step completed: outcome=${result.outcome}`);
|
|
158
175
|
} catch (error) {
|
|
159
176
|
core.setFailed(`agentic-step failed: ${error.message}`);
|
|
@@ -5,8 +5,8 @@
|
|
|
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,
|
|
9
|
-
import { dirname
|
|
8
|
+
import { writeFileSync, readFileSync, appendFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
9
|
+
import { dirname } from "path";
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import * as core from "@actions/core";
|
|
12
12
|
|
|
@@ -160,16 +160,6 @@ export function logActivity({
|
|
|
160
160
|
writeFileSync(filepath, `# intentïon Activity Log\n${entry}`);
|
|
161
161
|
}
|
|
162
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
|
-
}
|
|
173
163
|
}
|
|
174
164
|
|
|
175
165
|
/**
|
|
@@ -192,16 +182,19 @@ export function logActivity({
|
|
|
192
182
|
export function writeAgentLog({
|
|
193
183
|
task, outcome, model, durationMs, narrative,
|
|
194
184
|
reviewTable, completenessAdvice, contextNotes,
|
|
195
|
-
missionMetrics, tokensUsed,
|
|
185
|
+
missionMetrics, tokensUsed, sequence,
|
|
196
186
|
}) {
|
|
197
187
|
const now = new Date();
|
|
198
188
|
const stamp = now.toISOString().replace(/:/g, "-").replace(/\./g, "-");
|
|
199
|
-
|
|
189
|
+
// C4: Include zero-padded sequence number in filename
|
|
190
|
+
const seq = String(sequence || 0).padStart(3, "0");
|
|
191
|
+
const filename = `agent-log-${stamp}-${seq}.md`;
|
|
200
192
|
|
|
201
193
|
const parts = [
|
|
202
194
|
`# Agent Log: ${task} at ${now.toISOString()}`,
|
|
203
195
|
"",
|
|
204
196
|
"## Summary",
|
|
197
|
+
`**Sequence:** ${seq}`,
|
|
205
198
|
`**Task:** ${task}`,
|
|
206
199
|
`**Outcome:** ${outcome}`,
|
|
207
200
|
];
|
|
@@ -68,7 +68,6 @@ function detectDedicatedTests() {
|
|
|
68
68
|
function buildMetricAssessment(ctx, config) {
|
|
69
69
|
const thresholds = config.missionCompleteThresholds || {};
|
|
70
70
|
const minResolved = thresholds.minResolvedIssues ?? 3;
|
|
71
|
-
const minTests = thresholds.minDedicatedTests ?? 1;
|
|
72
71
|
const maxTodos = thresholds.maxSourceTodos ?? 0;
|
|
73
72
|
|
|
74
73
|
// Implementation review gaps (passed from workflow via env)
|
|
@@ -79,12 +78,13 @@ function buildMetricAssessment(ctx, config) {
|
|
|
79
78
|
} catch { /* ignore parse errors */ }
|
|
80
79
|
const criticalGaps = reviewGaps.filter((g) => g.severity === "critical");
|
|
81
80
|
|
|
81
|
+
// C6: Removed "Dedicated tests" metric; using cumulative transforms instead
|
|
82
82
|
const metrics = [
|
|
83
83
|
{ metric: "Open issues", value: ctx.issuesSummary.length, target: 0, met: ctx.issuesSummary.length === 0 },
|
|
84
84
|
{ metric: "Open PRs", value: ctx.prsSummary.length, target: 0, met: ctx.prsSummary.length === 0 },
|
|
85
85
|
{ metric: "Issues resolved", value: ctx.resolvedCount, target: minResolved, met: ctx.resolvedCount >= minResolved },
|
|
86
|
-
{ metric: "Dedicated tests", value: ctx.dedicatedTestCount, target: minTests, met: ctx.dedicatedTestCount >= minTests },
|
|
87
86
|
{ metric: "Source TODOs", value: ctx.sourceTodoCount, target: maxTodos, met: ctx.sourceTodoCount <= maxTodos },
|
|
87
|
+
{ metric: "Cumulative transforms", value: ctx.cumulativeTransformationCost, target: 1, met: ctx.cumulativeTransformationCost >= 1 },
|
|
88
88
|
{ metric: "Budget", value: ctx.cumulativeTransformationCost, target: ctx.transformationBudget || "unlimited", met: !(ctx.transformationBudget > 0 && ctx.cumulativeTransformationCost >= ctx.transformationBudget) },
|
|
89
89
|
{ metric: "Implementation review", value: criticalGaps.length === 0 ? "No critical gaps" : `${criticalGaps.length} critical gap(s)`, target: "No critical gaps", met: criticalGaps.length === 0 },
|
|
90
90
|
];
|
|
@@ -129,8 +129,8 @@ function buildPrompt(ctx, agentInstructions, metricAssessment) {
|
|
|
129
129
|
`Open issues: ${ctx.issuesSummary.length}`,
|
|
130
130
|
`Recently closed issues: ${ctx.recentlyClosedSummary.length}`,
|
|
131
131
|
`Open PRs: ${ctx.prsSummary.length}`,
|
|
132
|
-
`Dedicated test files: ${ctx.dedicatedTestCount}`,
|
|
133
132
|
`Source TODOs: ${ctx.sourceTodoCount}`,
|
|
133
|
+
`Cumulative transforms: ${ctx.cumulativeTransformationCost}`,
|
|
134
134
|
`Transformation budget: ${ctx.cumulativeTransformationCost}/${ctx.transformationBudget || "unlimited"}`,
|
|
135
135
|
"",
|
|
136
136
|
...(process.env.REVIEW_ADVICE ? [
|
|
@@ -216,18 +216,29 @@ async function executeMissionComplete(octokit, repo, reason) {
|
|
|
216
216
|
/**
|
|
217
217
|
* Execute mission-failed: write signal file and commit via Contents API.
|
|
218
218
|
*/
|
|
219
|
-
async function executeMissionFailed(octokit, repo, reason) {
|
|
219
|
+
async function executeMissionFailed(octokit, repo, reason, metricAssessment) {
|
|
220
|
+
// C16: Build a detailed reason including specific failed metrics
|
|
221
|
+
const metricDetail = metricAssessment?.notMet?.length > 0
|
|
222
|
+
? `Failed metrics: ${metricAssessment.notMet.map(m => `${m.metric}=${m.value} (target: ${typeof m.target === "number" ? (m.metric.includes("TODO") ? `<= ${m.target}` : `>= ${m.target}`) : m.target})`).join(", ")}.`
|
|
223
|
+
: reason;
|
|
224
|
+
const metsMet = metricAssessment?.metrics?.filter(m => m.met).map(m => `${m.metric}=${m.value}`) || [];
|
|
225
|
+
const detailedReason = metsMet.length > 0
|
|
226
|
+
? `${metricDetail} Passing metrics: ${metsMet.join(", ")}.`
|
|
227
|
+
: metricDetail;
|
|
228
|
+
|
|
220
229
|
const signal = [
|
|
221
230
|
"# Mission Failed",
|
|
222
231
|
"",
|
|
223
232
|
`- **Timestamp:** ${new Date().toISOString()}`,
|
|
224
233
|
`- **Detected by:** director`,
|
|
225
|
-
`- **Reason:** ${
|
|
234
|
+
`- **Reason:** ${detailedReason}`,
|
|
226
235
|
"",
|
|
227
236
|
"This file was created automatically. To restart, delete this file and run `npx @xn-intenton-z2a/agentic-lib init --reseed`.",
|
|
228
237
|
].join("\n");
|
|
229
238
|
writeFileSync("MISSION_FAILED.md", signal);
|
|
230
239
|
|
|
240
|
+
// C16: Use detailed commit message
|
|
241
|
+
const commitMsg = `mission-failed: ${metricDetail.substring(0, 200)}`;
|
|
231
242
|
try {
|
|
232
243
|
const contentBase64 = Buffer.from(signal).toString("base64");
|
|
233
244
|
let existingSha;
|
|
@@ -238,7 +249,7 @@ async function executeMissionFailed(octokit, repo, reason) {
|
|
|
238
249
|
await octokit.rest.repos.createOrUpdateFileContents({
|
|
239
250
|
...repo,
|
|
240
251
|
path: "MISSION_FAILED.md",
|
|
241
|
-
message:
|
|
252
|
+
message: commitMsg,
|
|
242
253
|
content: contentBase64,
|
|
243
254
|
branch: "main",
|
|
244
255
|
...(existingSha ? { sha: existingSha } : {}),
|
|
@@ -247,6 +258,33 @@ async function executeMissionFailed(octokit, repo, reason) {
|
|
|
247
258
|
} catch (err) {
|
|
248
259
|
core.warning(`Could not commit MISSION_FAILED.md: ${err.message}`);
|
|
249
260
|
}
|
|
261
|
+
|
|
262
|
+
// C3: Auto-disable schedule on mission-failed
|
|
263
|
+
try {
|
|
264
|
+
const { readState, writeState } = await import("../../../copilot/state.js");
|
|
265
|
+
const state = readState(".");
|
|
266
|
+
state.status["mission-failed"] = true;
|
|
267
|
+
state.status["mission-failed-reason"] = metricDetail.substring(0, 500);
|
|
268
|
+
state.schedule["auto-disabled"] = true;
|
|
269
|
+
state.schedule["auto-disabled-reason"] = "mission-failed";
|
|
270
|
+
writeState(".", state);
|
|
271
|
+
core.info("State updated: mission-failed, schedule auto-disabled");
|
|
272
|
+
} catch (err) {
|
|
273
|
+
core.warning(`Could not update state for mission-failed: ${err.message}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// C3: Dispatch schedule change to weekly
|
|
277
|
+
try {
|
|
278
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
279
|
+
...repo,
|
|
280
|
+
workflow_id: "agentic-lib-schedule.yml",
|
|
281
|
+
ref: "main",
|
|
282
|
+
inputs: { frequency: "weekly" },
|
|
283
|
+
});
|
|
284
|
+
core.info("Dispatched schedule change to weekly after mission-failed");
|
|
285
|
+
} catch (err) {
|
|
286
|
+
core.warning(`Could not dispatch schedule change: ${err.message}`);
|
|
287
|
+
}
|
|
250
288
|
}
|
|
251
289
|
|
|
252
290
|
/**
|
|
@@ -261,10 +299,13 @@ export async function direct(context) {
|
|
|
261
299
|
|
|
262
300
|
// --- Gather context (similar to supervisor but focused on metrics) ---
|
|
263
301
|
const mission = readOptionalFile(config.paths.mission.path);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
302
|
+
// C2: Read cumulative cost from persistent state
|
|
303
|
+
let cumulativeTransformationCost = 0;
|
|
304
|
+
try {
|
|
305
|
+
const { readState } = await import("../../../copilot/state.js");
|
|
306
|
+
const state = readState(".");
|
|
307
|
+
cumulativeTransformationCost = state.counters["cumulative-transforms"] || 0;
|
|
308
|
+
} catch { /* state not available yet */ }
|
|
268
309
|
|
|
269
310
|
const missionComplete = existsSync("MISSION_COMPLETE.md");
|
|
270
311
|
const missionFailed = existsSync("MISSION_FAILED.md");
|
|
@@ -451,7 +492,7 @@ export async function direct(context) {
|
|
|
451
492
|
outcome = "directed";
|
|
452
493
|
} else if (decision === "mission-failed") {
|
|
453
494
|
if (process.env.GITHUB_REPOSITORY !== "xn-intenton-z2a/agentic-lib") {
|
|
454
|
-
await executeMissionFailed(octokit, repo, reason);
|
|
495
|
+
await executeMissionFailed(octokit, repo, reason, metricAssessment);
|
|
455
496
|
outcome = "mission-failed";
|
|
456
497
|
}
|
|
457
498
|
}
|
|
@@ -58,6 +58,8 @@ export async function maintainFeatures(context) {
|
|
|
58
58
|
return { outcome: "wip-limit-reached", details: `Maintenance WIP limit reached (${wipCheck.count}/${config.maintenanceIssuesWipLimit})` };
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
const maxTokens = config.maxTokensPerMaintain || 200000;
|
|
62
|
+
|
|
61
63
|
const mission = readOptionalFile(config.paths.mission.path);
|
|
62
64
|
const featureLimit = config.paths.features.limit;
|
|
63
65
|
const featureFiles = buildFileListing(config.paths.features.path, ".md");
|
|
@@ -89,6 +91,7 @@ export async function maintainFeatures(context) {
|
|
|
89
91
|
"## Constraints",
|
|
90
92
|
`- Maximum ${featureLimit} feature files`,
|
|
91
93
|
"- Feature files must be markdown with a descriptive filename (e.g. HTTP_SERVER.md)",
|
|
94
|
+
`- Token budget: ~${maxTokens} tokens. Be concise — avoid verbose explanations or unnecessary tool calls.`,
|
|
92
95
|
].join("\n");
|
|
93
96
|
|
|
94
97
|
const systemPrompt =
|
|
@@ -103,6 +106,9 @@ export async function maintainFeatures(context) {
|
|
|
103
106
|
if (logFilePath) attachments.push({ type: "file", path: logFilePath });
|
|
104
107
|
if (screenshotFilePath) attachments.push({ type: "file", path: screenshotFilePath });
|
|
105
108
|
|
|
109
|
+
// Derive a tool-call cap from the token budget (rough: ~5000 tokens per tool call)
|
|
110
|
+
const maxToolCalls = Math.max(10, Math.floor(maxTokens / 5000));
|
|
111
|
+
|
|
106
112
|
const result = await runCopilotSession({
|
|
107
113
|
workspacePath: process.cwd(),
|
|
108
114
|
model,
|
|
@@ -112,6 +118,7 @@ export async function maintainFeatures(context) {
|
|
|
112
118
|
writablePaths,
|
|
113
119
|
createTools,
|
|
114
120
|
attachments,
|
|
121
|
+
maxToolCalls,
|
|
115
122
|
excludedTools: ["dispatch_workflow", "close_issue", "label_issue", "post_discussion_comment", "run_tests"],
|
|
116
123
|
logger: { info: core.info, warning: core.warning, error: core.error, debug: core.debug },
|
|
117
124
|
});
|
|
@@ -51,6 +51,8 @@ export async function maintainLibrary(context) {
|
|
|
51
51
|
return { outcome: "nop", details: "Mission already complete (MISSION_COMPLETE.md signal)" };
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const maxTokens = config.maxTokensPerMaintain || 200000;
|
|
55
|
+
|
|
54
56
|
const sourcesPath = config.paths.librarySources.path;
|
|
55
57
|
const sources = readOptionalFile(sourcesPath);
|
|
56
58
|
const mission = readOptionalFile(config.paths.mission.path);
|
|
@@ -83,6 +85,9 @@ export async function maintainLibrary(context) {
|
|
|
83
85
|
`Write the URLs as a markdown list in ${sourcesPath}, keeping the existing header text.`,
|
|
84
86
|
"",
|
|
85
87
|
formatPathsSection(writablePaths, config.readOnlyPaths, config),
|
|
88
|
+
"",
|
|
89
|
+
"## Constraints",
|
|
90
|
+
`- Token budget: ~${maxTokens} tokens. Be concise — avoid verbose explanations or unnecessary tool calls.`,
|
|
86
91
|
].join("\n");
|
|
87
92
|
} else {
|
|
88
93
|
prompt = [
|
|
@@ -105,6 +110,7 @@ export async function maintainLibrary(context) {
|
|
|
105
110
|
"",
|
|
106
111
|
"## Constraints",
|
|
107
112
|
`- Maximum ${libraryLimit} library documents`,
|
|
113
|
+
`- Token budget: ~${maxTokens} tokens. Be concise — avoid verbose explanations or unnecessary tool calls.`,
|
|
108
114
|
].join("\n");
|
|
109
115
|
}
|
|
110
116
|
|
|
@@ -116,6 +122,9 @@ export async function maintainLibrary(context) {
|
|
|
116
122
|
if (logFilePath) attachments.push({ type: "file", path: logFilePath });
|
|
117
123
|
if (screenshotFilePath) attachments.push({ type: "file", path: screenshotFilePath });
|
|
118
124
|
|
|
125
|
+
// Derive a tool-call cap from the token budget (rough: ~5000 tokens per tool call)
|
|
126
|
+
const maxToolCalls = Math.max(10, Math.floor(maxTokens / 5000));
|
|
127
|
+
|
|
119
128
|
const result = await runCopilotSession({
|
|
120
129
|
workspacePath: process.cwd(),
|
|
121
130
|
model,
|
|
@@ -124,6 +133,7 @@ export async function maintainLibrary(context) {
|
|
|
124
133
|
userPrompt: prompt,
|
|
125
134
|
writablePaths,
|
|
126
135
|
attachments,
|
|
136
|
+
maxToolCalls,
|
|
127
137
|
excludedTools: ["dispatch_workflow", "close_issue", "label_issue", "post_discussion_comment", "run_tests"],
|
|
128
138
|
logger: { info: core.info, warning: core.warning, error: core.error, debug: core.debug },
|
|
129
139
|
});
|
|
@@ -106,12 +106,20 @@ async function postDirectReply(octokit, repo, nodeId, body) {
|
|
|
106
106
|
|
|
107
107
|
async function gatherContext(octokit, repo, config, t) {
|
|
108
108
|
const mission = readOptionalFile(config.paths.mission.path);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
// Read recent activity from agent-log files
|
|
110
|
+
let recentActivity = "";
|
|
111
|
+
let cumulativeTransformationCost = 0;
|
|
112
|
+
try {
|
|
113
|
+
const { readdirSync } = await import("fs");
|
|
114
|
+
const logFiles = readdirSync(".").filter(f => f.startsWith("agent-log-") && f.endsWith(".md")).sort();
|
|
115
|
+
const recent = logFiles.slice(-5);
|
|
116
|
+
recentActivity = recent.map(f => readOptionalFile(f)).join("\n---\n").split("\n").slice(-40).join("\n");
|
|
117
|
+
for (const f of logFiles) {
|
|
118
|
+
const content = readOptionalFile(f);
|
|
119
|
+
const costMatches = content.matchAll(/\*\*agentic-lib transformation cost:\*\* (\d+)/g);
|
|
120
|
+
cumulativeTransformationCost += [...costMatches].reduce((sum, m) => sum + parseInt(m[1], 10), 0);
|
|
121
|
+
}
|
|
122
|
+
} catch { /* no agent-log files yet */ }
|
|
115
123
|
|
|
116
124
|
// Check mission-complete signal
|
|
117
125
|
const missionComplete = existsSync("MISSION_COMPLETE.md");
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// instead of having all context front-loaded into the prompt.
|
|
7
7
|
|
|
8
8
|
import * as core from "@actions/core";
|
|
9
|
-
import { existsSync, readdirSync, statSync } from "fs";
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
10
10
|
import { join, resolve } from "path";
|
|
11
11
|
import { readOptionalFile, formatPathsSection, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
|
|
12
12
|
import { runCopilotSession } from "../../../copilot/copilot-session.js";
|
|
@@ -38,6 +38,35 @@ function buildFileListing(dirPath, extensions) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Build a library index: filename + first 2 lines of each library doc, capped at 2000 chars.
|
|
43
|
+
*/
|
|
44
|
+
function buildLibraryIndex(libraryPath) {
|
|
45
|
+
if (!libraryPath || !existsSync(libraryPath)) return "";
|
|
46
|
+
try {
|
|
47
|
+
const files = readdirSync(libraryPath).filter((f) => f.endsWith(".md")).sort();
|
|
48
|
+
if (files.length === 0) return "";
|
|
49
|
+
const entries = [];
|
|
50
|
+
let totalLen = 0;
|
|
51
|
+
for (const f of files) {
|
|
52
|
+
const fullPath = join(libraryPath, f);
|
|
53
|
+
try {
|
|
54
|
+
const content = readFileSync(fullPath, "utf8");
|
|
55
|
+
const lines = content.split("\n").slice(0, 2).join(" ").trim();
|
|
56
|
+
const entry = `- ${f}: ${lines}`;
|
|
57
|
+
if (totalLen + entry.length > 2000) break;
|
|
58
|
+
entries.push(entry);
|
|
59
|
+
totalLen += entry.length;
|
|
60
|
+
} catch {
|
|
61
|
+
entries.push(`- ${f}: (unreadable)`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return entries.join("\n");
|
|
65
|
+
} catch {
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
/**
|
|
42
71
|
* Run the full transformation pipeline from mission to code.
|
|
43
72
|
*
|
|
@@ -89,6 +118,7 @@ export async function transform(context) {
|
|
|
89
118
|
const testFiles = buildFileListing(config.paths.tests.path, [".js", ".ts"]);
|
|
90
119
|
const webFiles = buildFileListing(config.paths.web?.path || "src/web/", [".html", ".css", ".js"]);
|
|
91
120
|
const featureFiles = buildFileListing(config.paths.features.path, [".md"]);
|
|
121
|
+
const libraryIndex = buildLibraryIndex(config.paths.library?.path || "library/");
|
|
92
122
|
|
|
93
123
|
const prompt = [
|
|
94
124
|
"## Instructions",
|
|
@@ -107,6 +137,12 @@ export async function transform(context) {
|
|
|
107
137
|
"The website in `src/web/` uses the JS library. `src/web/lib.js` re-exports from `../lib/main.js`.",
|
|
108
138
|
"When transforming source code, also update the website to use the library's new/changed features.",
|
|
109
139
|
] : []),
|
|
140
|
+
...(libraryIndex ? [
|
|
141
|
+
"",
|
|
142
|
+
"## Library Index",
|
|
143
|
+
"Reference documents available in `library/` (use read_file for full content):",
|
|
144
|
+
libraryIndex,
|
|
145
|
+
] : []),
|
|
110
146
|
"",
|
|
111
147
|
"## Your Task",
|
|
112
148
|
"Analyze the mission and open issues (use list_issues tool).",
|
|
@@ -29,7 +29,8 @@ runs:
|
|
|
29
29
|
# Unstage workflow files — GITHUB_TOKEN cannot push workflow changes
|
|
30
30
|
git reset HEAD -- '.github/workflows/' 2>/dev/null || true
|
|
31
31
|
# Unstage log/screenshot files — these live on the agentic-lib-logs branch
|
|
32
|
-
git reset HEAD -- 'intentïon.md' '
|
|
32
|
+
git reset HEAD -- 'intentïon.md' 'SCREENSHOT_INDEX.png' 2>/dev/null || true
|
|
33
|
+
git reset HEAD -- agent-log-*.md 2>/dev/null || true
|
|
33
34
|
if git diff --cached --quiet; then
|
|
34
35
|
echo "No changes to commit"
|
|
35
36
|
fi
|
package/src/copilot/config.js
CHANGED
|
@@ -240,11 +240,10 @@ export function loadConfig(configPath) {
|
|
|
240
240
|
const bot = toml.bot || {};
|
|
241
241
|
|
|
242
242
|
// Mission-complete thresholds (with safe defaults)
|
|
243
|
+
// C6: Removed minDedicatedTests and requireDedicatedTests
|
|
243
244
|
const mc = toml["mission-complete"] || {};
|
|
244
245
|
const missionCompleteThresholds = {
|
|
245
246
|
minResolvedIssues: mc["min-resolved-issues"] ?? 3,
|
|
246
|
-
minDedicatedTests: mc["min-dedicated-tests"] ?? 1,
|
|
247
|
-
requireDedicatedTests: mc["require-dedicated-tests"] ?? true,
|
|
248
247
|
maxSourceTodos: mc["max-source-todos"] ?? 0,
|
|
249
248
|
};
|
|
250
249
|
|
|
@@ -261,13 +260,14 @@ export function loadConfig(configPath) {
|
|
|
261
260
|
transformationBudget: tuning.transformationBudget,
|
|
262
261
|
seeding: toml.seeding || {},
|
|
263
262
|
intentionBot: {
|
|
264
|
-
|
|
263
|
+
logPrefix: bot["log-prefix"] || "agent-log-",
|
|
265
264
|
logBranch: bot["log-branch"] || "agentic-lib-logs",
|
|
266
265
|
screenshotFile: bot["screenshot-file"] || "SCREENSHOT_INDEX.png",
|
|
267
266
|
},
|
|
268
267
|
init: toml.init || null,
|
|
269
268
|
tdd: toml.tdd === true,
|
|
270
269
|
missionCompleteThresholds,
|
|
270
|
+
maxTokensPerMaintain: resolvedLimits.maxTokensPerMaintain || 200000,
|
|
271
271
|
writablePaths,
|
|
272
272
|
readOnlyPaths,
|
|
273
273
|
configToml: rawToml,
|
package/src/copilot/guards.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
// (transform.js, fix-code.js, maintain-features.js, maintain-library.js)
|
|
7
7
|
// before Phase 4 convergence replaced them with unconditional runCopilotSession().
|
|
8
8
|
|
|
9
|
-
import { existsSync } from "fs";
|
|
9
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
10
10
|
import { resolve } from "path";
|
|
11
11
|
import { execSync } from "child_process";
|
|
12
|
-
import {
|
|
12
|
+
import { readState } from "./state.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Task-to-guard mapping. Each task has an ordered list of guards.
|
|
@@ -71,9 +71,9 @@ export function checkGuards(taskName, config, workspacePath, { logger } = {}) {
|
|
|
71
71
|
case "budget-exhausted": {
|
|
72
72
|
const budget = config.transformationBudget || 0;
|
|
73
73
|
if (budget > 0) {
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const cumulativeCost =
|
|
74
|
+
// C2: Read cumulative cost from persistent state (agentic-lib-state.toml)
|
|
75
|
+
const state = readState(wsPath);
|
|
76
|
+
const cumulativeCost = state.budget["transformation-budget-used"] || 0;
|
|
77
77
|
if (cumulativeCost >= budget) {
|
|
78
78
|
return { skip: true, reason: `Transformation budget exhausted (${cumulativeCost}/${budget})` };
|
|
79
79
|
}
|