@xn-intenton-z2a/agentic-lib 7.1.70 → 7.1.72
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/workflows/agentic-lib-init.yml +0 -2
- package/bin/agentic-lib.js +283 -70
- package/package.json +1 -1
- package/src/actions/agentic-step/config-loader.js +1 -0
- package/src/actions/agentic-step/index.js +55 -14
- package/src/actions/agentic-step/tasks/discussions.js +138 -4
- package/src/actions/agentic-step/tasks/review-issue.js +21 -0
- package/src/actions/agentic-step/tasks/supervise.js +234 -4
- package/src/actions/commit-if-changed/action.yml +2 -0
- package/src/agents/agent-supervisor.md +31 -9
- package/src/seeds/zero-index.html +0 -13
- package/src/seeds/zero-package.json +1 -1
- package/src/seeds/zero-web.test.js +0 -5
package/bin/agentic-lib.js
CHANGED
|
@@ -856,10 +856,30 @@ function clearDirContents(dirPath, label) {
|
|
|
856
856
|
}
|
|
857
857
|
}
|
|
858
858
|
|
|
859
|
-
function initReseed() {
|
|
859
|
+
function initReseed(initTimestamp) {
|
|
860
860
|
console.log("\n--- Reseed: Clear Features + Activity Log ---");
|
|
861
861
|
removeFile(resolve(target, "intentïon.md"), "intentïon.md");
|
|
862
862
|
removeFile(resolve(target, "MISSION_COMPLETE.md"), "MISSION_COMPLETE.md");
|
|
863
|
+
removeFile(resolve(target, "MISSION_FAILED.md"), "MISSION_FAILED.md");
|
|
864
|
+
|
|
865
|
+
// Write init epoch header to the activity log
|
|
866
|
+
const pkg = JSON.parse(readFileSync(resolve(pkgRoot, "package.json"), "utf8"));
|
|
867
|
+
const mode = purge ? "purge" : "reseed";
|
|
868
|
+
const initHeader = [
|
|
869
|
+
`# intentïon activity log`,
|
|
870
|
+
"",
|
|
871
|
+
`**init ${mode}** at ${initTimestamp} (agentic-lib@${pkg.version})`,
|
|
872
|
+
`**mission:** ${mission}`,
|
|
873
|
+
"",
|
|
874
|
+
"---",
|
|
875
|
+
"",
|
|
876
|
+
].join("\n");
|
|
877
|
+
if (!dryRun) {
|
|
878
|
+
writeFileSync(resolve(target, "intentïon.md"), initHeader);
|
|
879
|
+
}
|
|
880
|
+
console.log(" WRITE: intentïon.md (init epoch header)");
|
|
881
|
+
initChanges++;
|
|
882
|
+
|
|
863
883
|
clearDirContents(resolve(target, "features"), "features");
|
|
864
884
|
|
|
865
885
|
// Clear old features location if it exists
|
|
@@ -916,7 +936,7 @@ function clearAndRecreateDir(dirPath, label) {
|
|
|
916
936
|
if (!dryRun) mkdirSync(fullPath, { recursive: true });
|
|
917
937
|
}
|
|
918
938
|
|
|
919
|
-
function initPurge(seedsDir, missionName) {
|
|
939
|
+
function initPurge(seedsDir, missionName, initTimestamp) {
|
|
920
940
|
console.log("\n--- Purge: Reset Source Files to Seed State ---");
|
|
921
941
|
|
|
922
942
|
const { sourcePath, testsPath, examplesPath, webPath } = readTomlPaths();
|
|
@@ -980,10 +1000,46 @@ function initPurge(seedsDir, missionName) {
|
|
|
980
1000
|
}
|
|
981
1001
|
process.exit(1);
|
|
982
1002
|
}
|
|
1003
|
+
|
|
1004
|
+
// Write init metadata to agentic-lib.toml
|
|
1005
|
+
const tomlTarget = resolve(target, "agentic-lib.toml");
|
|
1006
|
+
if (existsSync(tomlTarget)) {
|
|
1007
|
+
let toml = readFileSync(tomlTarget, "utf8");
|
|
1008
|
+
const pkg = JSON.parse(readFileSync(resolve(pkgRoot, "package.json"), "utf8"));
|
|
1009
|
+
const initSection = [
|
|
1010
|
+
"",
|
|
1011
|
+
"[init]",
|
|
1012
|
+
`timestamp = "${initTimestamp}"`,
|
|
1013
|
+
`mode = "purge"`,
|
|
1014
|
+
`mission = "${missionName}"`,
|
|
1015
|
+
`version = "${pkg.version}"`,
|
|
1016
|
+
].join("\n");
|
|
1017
|
+
// Replace existing [init] section or append
|
|
1018
|
+
if (/^\[init\]/m.test(toml)) {
|
|
1019
|
+
toml = toml.replace(/\n?\[init\][^\[]*/, initSection);
|
|
1020
|
+
} else {
|
|
1021
|
+
toml = toml.trimEnd() + "\n" + initSection + "\n";
|
|
1022
|
+
}
|
|
1023
|
+
if (!dryRun) {
|
|
1024
|
+
writeFileSync(tomlTarget, toml);
|
|
1025
|
+
}
|
|
1026
|
+
console.log(" WRITE: [init] section in agentic-lib.toml");
|
|
1027
|
+
initChanges++;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function ghExec(cmd, input) {
|
|
1032
|
+
const opts = { cwd: target, encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] };
|
|
1033
|
+
if (input) opts.input = input;
|
|
1034
|
+
return execSync(cmd, opts);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function ghGraphQL(query) {
|
|
1038
|
+
return JSON.parse(ghExec("gh api graphql --input -", JSON.stringify({ query })));
|
|
983
1039
|
}
|
|
984
1040
|
|
|
985
1041
|
function initPurgeGitHub() {
|
|
986
|
-
console.log("\n--- Purge:
|
|
1042
|
+
console.log("\n--- Purge: Clean Slate (Issues, PRs, Runs, Branches, Labels, Discussions) ---");
|
|
987
1043
|
|
|
988
1044
|
// Detect the GitHub repo from git remote
|
|
989
1045
|
let repoSlug = "";
|
|
@@ -993,7 +1049,6 @@ function initPurgeGitHub() {
|
|
|
993
1049
|
encoding: "utf8",
|
|
994
1050
|
timeout: 10000,
|
|
995
1051
|
}).trim();
|
|
996
|
-
// Parse owner/repo from git@github.com:owner/repo.git or https://github.com/owner/repo.git
|
|
997
1052
|
const match = remoteUrl.match(/github\.com[:/]([^/]+\/[^/.]+)/);
|
|
998
1053
|
if (match) repoSlug = match[1].replace(/\.git$/, "");
|
|
999
1054
|
} catch {
|
|
@@ -1005,7 +1060,6 @@ function initPurgeGitHub() {
|
|
|
1005
1060
|
return;
|
|
1006
1061
|
}
|
|
1007
1062
|
|
|
1008
|
-
// Check gh CLI is available
|
|
1009
1063
|
try {
|
|
1010
1064
|
execSync("gh --version", { encoding: "utf8", timeout: 5000, stdio: "pipe" });
|
|
1011
1065
|
} catch {
|
|
@@ -1013,29 +1067,205 @@ function initPurgeGitHub() {
|
|
|
1013
1067
|
return;
|
|
1014
1068
|
}
|
|
1015
1069
|
|
|
1016
|
-
|
|
1070
|
+
const [owner, repo] = repoSlug.split("/");
|
|
1071
|
+
|
|
1072
|
+
// ── A1: Close + lock ALL issues (open and closed) ──────────────────
|
|
1073
|
+
console.log("\n --- Issues: close open, lock all ---");
|
|
1074
|
+
try {
|
|
1075
|
+
// Close all open issues with not_planned reason
|
|
1076
|
+
const openIssuesJson = ghExec(`gh api repos/${repoSlug}/issues?state=open&per_page=100`);
|
|
1077
|
+
const openIssues = JSON.parse(openIssuesJson || "[]").filter((i) => !i.pull_request);
|
|
1078
|
+
for (const issue of openIssues) {
|
|
1079
|
+
console.log(` CLOSE: issue #${issue.number} — ${issue.title}`);
|
|
1080
|
+
if (!dryRun) {
|
|
1081
|
+
try {
|
|
1082
|
+
ghExec(
|
|
1083
|
+
`gh api repos/${repoSlug}/issues/${issue.number} -X PATCH -f state=closed -f state_reason=not_planned`,
|
|
1084
|
+
);
|
|
1085
|
+
ghExec(
|
|
1086
|
+
`gh api repos/${repoSlug}/issues/${issue.number}/comments -X POST -f body="Closed by init --purge (mission reset)"`,
|
|
1087
|
+
);
|
|
1088
|
+
initChanges++;
|
|
1089
|
+
} catch (err) {
|
|
1090
|
+
console.log(` WARN: Failed to close issue #${issue.number}: ${err.message}`);
|
|
1091
|
+
}
|
|
1092
|
+
} else {
|
|
1093
|
+
initChanges++;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (openIssues.length === 0) console.log(" No open issues to close");
|
|
1097
|
+
|
|
1098
|
+
// Lock ALL issues (open and closed) to prevent bleed
|
|
1099
|
+
const allIssuesJson = ghExec(`gh api repos/${repoSlug}/issues?state=all&per_page=100`);
|
|
1100
|
+
const allIssues = JSON.parse(allIssuesJson || "[]").filter((i) => !i.pull_request && !i.locked);
|
|
1101
|
+
for (const issue of allIssues) {
|
|
1102
|
+
console.log(` LOCK: issue #${issue.number} — ${issue.title}`);
|
|
1103
|
+
if (!dryRun) {
|
|
1104
|
+
try {
|
|
1105
|
+
ghExec(
|
|
1106
|
+
`gh api repos/${repoSlug}/issues/${issue.number}/lock -X PUT -f lock_reason=resolved`,
|
|
1107
|
+
);
|
|
1108
|
+
initChanges++;
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
console.log(` WARN: Failed to lock issue #${issue.number}: ${err.message}`);
|
|
1111
|
+
}
|
|
1112
|
+
} else {
|
|
1113
|
+
initChanges++;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (allIssues.length === 0) console.log(" No unlocked issues to lock");
|
|
1117
|
+
} catch (err) {
|
|
1118
|
+
console.log(` WARN: Issue cleanup failed: ${err.message}`);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// ── A2: Close all open PRs + delete branches ──────────────────────
|
|
1122
|
+
console.log("\n --- PRs: close all open ---");
|
|
1123
|
+
try {
|
|
1124
|
+
const prsJson = ghExec(`gh pr list --repo ${repoSlug} --state open --json number,title,headRefName --limit 100`);
|
|
1125
|
+
const prs = JSON.parse(prsJson || "[]");
|
|
1126
|
+
for (const pr of prs) {
|
|
1127
|
+
console.log(` CLOSE: PR #${pr.number} — ${pr.title} (${pr.headRefName})`);
|
|
1128
|
+
if (!dryRun) {
|
|
1129
|
+
try {
|
|
1130
|
+
ghExec(`gh pr close ${pr.number} --repo ${repoSlug} --delete-branch`);
|
|
1131
|
+
initChanges++;
|
|
1132
|
+
} catch (err) {
|
|
1133
|
+
console.log(` WARN: Failed to close PR #${pr.number}: ${err.message}`);
|
|
1134
|
+
}
|
|
1135
|
+
} else {
|
|
1136
|
+
initChanges++;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (prs.length === 0) console.log(" No open PRs to close");
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
console.log(` WARN: PR cleanup failed: ${err.message}`);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// ── A3: Delete old workflow runs ──────────────────────────────────
|
|
1145
|
+
console.log("\n --- Workflow runs: delete old ---");
|
|
1017
1146
|
try {
|
|
1018
|
-
const
|
|
1147
|
+
const runsJson = ghExec(`gh api repos/${repoSlug}/actions/runs?per_page=100`);
|
|
1148
|
+
const runs = JSON.parse(runsJson || '{"workflow_runs":[]}').workflow_runs || [];
|
|
1149
|
+
let deleted = 0;
|
|
1150
|
+
for (const run of runs) {
|
|
1151
|
+
// Skip currently running workflows (in_progress or queued)
|
|
1152
|
+
if (run.status === "in_progress" || run.status === "queued") {
|
|
1153
|
+
console.log(` SKIP: run ${run.id} (${run.name}) — ${run.status}`);
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (!dryRun) {
|
|
1157
|
+
try {
|
|
1158
|
+
ghExec(`gh api repos/${repoSlug}/actions/runs/${run.id} -X DELETE`);
|
|
1159
|
+
deleted++;
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
console.log(` WARN: Failed to delete run ${run.id}: ${err.message}`);
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
deleted++;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
console.log(` ${deleted > 0 ? `DELETE: ${deleted} workflow run(s)` : "No workflow runs to delete"}`);
|
|
1168
|
+
initChanges += deleted;
|
|
1169
|
+
} catch (err) {
|
|
1170
|
+
console.log(` WARN: Workflow run cleanup failed: ${err.message}`);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// ── A4: Delete stale remote branches ──────────────────────────────
|
|
1174
|
+
console.log("\n --- Branches: delete stale remotes ---");
|
|
1175
|
+
try {
|
|
1176
|
+
const branchesJson = ghExec(`gh api repos/${repoSlug}/branches?per_page=100`);
|
|
1177
|
+
const branches = JSON.parse(branchesJson || "[]");
|
|
1178
|
+
const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
1019
1179
|
cwd: target,
|
|
1020
1180
|
encoding: "utf8",
|
|
1021
|
-
timeout:
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1181
|
+
timeout: 5000,
|
|
1182
|
+
}).trim();
|
|
1183
|
+
const keepBranches = new Set(["main", "master", "template", "gh-pages", currentBranch]);
|
|
1184
|
+
let deletedBranches = 0;
|
|
1185
|
+
for (const branch of branches) {
|
|
1186
|
+
if (keepBranches.has(branch.name)) continue;
|
|
1187
|
+
if (branch.protected) continue;
|
|
1188
|
+
console.log(` DELETE: branch ${branch.name}`);
|
|
1189
|
+
if (!dryRun) {
|
|
1190
|
+
try {
|
|
1191
|
+
ghExec(`gh api repos/${repoSlug}/git/refs/heads/${branch.name} -X DELETE`);
|
|
1192
|
+
deletedBranches++;
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
console.log(` WARN: Failed to delete branch ${branch.name}: ${err.message}`);
|
|
1195
|
+
}
|
|
1196
|
+
} else {
|
|
1197
|
+
deletedBranches++;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (deletedBranches === 0) console.log(" No stale branches to delete");
|
|
1201
|
+
initChanges += deletedBranches;
|
|
1202
|
+
} catch (err) {
|
|
1203
|
+
console.log(` WARN: Branch cleanup failed: ${err.message}`);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// ── A5: Reset labels ──────────────────────────────────────────────
|
|
1207
|
+
console.log("\n --- Labels: reset to pipeline defaults ---");
|
|
1208
|
+
const GITHUB_DEFAULT_LABELS = new Set([
|
|
1209
|
+
"bug",
|
|
1210
|
+
"documentation",
|
|
1211
|
+
"duplicate",
|
|
1212
|
+
"enhancement",
|
|
1213
|
+
"good first issue",
|
|
1214
|
+
"help wanted",
|
|
1215
|
+
"invalid",
|
|
1216
|
+
"question",
|
|
1217
|
+
"wontfix",
|
|
1218
|
+
]);
|
|
1219
|
+
const PIPELINE_LABELS = [
|
|
1220
|
+
{ name: "automated", color: "0e8a16", description: "Created by the autonomous pipeline" },
|
|
1221
|
+
{ name: "ready", color: "0075ca", description: "Issue is ready for transformation" },
|
|
1222
|
+
{ name: "in-progress", color: "e4e669", description: "Work in progress" },
|
|
1223
|
+
{ name: "merged", color: "6f42c1", description: "Associated PR has been merged" },
|
|
1224
|
+
{ name: "automerge", color: "1d76db", description: "PR should be auto-merged when checks pass" },
|
|
1225
|
+
];
|
|
1226
|
+
try {
|
|
1227
|
+
const labelsJson = ghExec(`gh api repos/${repoSlug}/labels?per_page=100`);
|
|
1228
|
+
const labels = JSON.parse(labelsJson || "[]");
|
|
1229
|
+
const pipelineNames = new Set(PIPELINE_LABELS.map((l) => l.name));
|
|
1230
|
+
// Delete non-default, non-pipeline labels
|
|
1231
|
+
for (const label of labels) {
|
|
1232
|
+
if (GITHUB_DEFAULT_LABELS.has(label.name)) continue;
|
|
1233
|
+
if (pipelineNames.has(label.name)) continue;
|
|
1234
|
+
console.log(` DELETE: label "${label.name}"`);
|
|
1235
|
+
if (!dryRun) {
|
|
1236
|
+
try {
|
|
1237
|
+
ghExec(`gh api repos/${repoSlug}/labels/${encodeURIComponent(label.name)} -X DELETE`);
|
|
1238
|
+
initChanges++;
|
|
1239
|
+
} catch (err) {
|
|
1240
|
+
console.log(` WARN: Failed to delete label "${label.name}": ${err.message}`);
|
|
1241
|
+
}
|
|
1242
|
+
} else {
|
|
1243
|
+
initChanges++;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
// Ensure pipeline labels exist with correct config
|
|
1247
|
+
const existingNames = new Set(labels.map((l) => l.name));
|
|
1248
|
+
for (const pl of PIPELINE_LABELS) {
|
|
1249
|
+
if (existingNames.has(pl.name)) {
|
|
1250
|
+
// Update to ensure correct color/description
|
|
1030
1251
|
if (!dryRun) {
|
|
1031
1252
|
try {
|
|
1032
|
-
|
|
1033
|
-
`gh
|
|
1034
|
-
|
|
1253
|
+
ghExec(
|
|
1254
|
+
`gh api repos/${repoSlug}/labels/${encodeURIComponent(pl.name)} -X PATCH -f color=${pl.color} -f description="${pl.description}"`,
|
|
1255
|
+
);
|
|
1256
|
+
} catch { /* ignore */ }
|
|
1257
|
+
}
|
|
1258
|
+
console.log(` UPDATE: label "${pl.name}"`);
|
|
1259
|
+
} else {
|
|
1260
|
+
console.log(` CREATE: label "${pl.name}"`);
|
|
1261
|
+
if (!dryRun) {
|
|
1262
|
+
try {
|
|
1263
|
+
ghExec(
|
|
1264
|
+
`gh api repos/${repoSlug}/labels -X POST -f name="${pl.name}" -f color=${pl.color} -f description="${pl.description}"`,
|
|
1035
1265
|
);
|
|
1036
1266
|
initChanges++;
|
|
1037
1267
|
} catch (err) {
|
|
1038
|
-
console.log(` WARN: Failed to
|
|
1268
|
+
console.log(` WARN: Failed to create label "${pl.name}": ${err.message}`);
|
|
1039
1269
|
}
|
|
1040
1270
|
} else {
|
|
1041
1271
|
initChanges++;
|
|
@@ -1043,23 +1273,15 @@ function initPurgeGitHub() {
|
|
|
1043
1273
|
}
|
|
1044
1274
|
}
|
|
1045
1275
|
} catch (err) {
|
|
1046
|
-
console.log(` WARN:
|
|
1276
|
+
console.log(` WARN: Label cleanup failed: ${err.message}`);
|
|
1047
1277
|
}
|
|
1048
1278
|
|
|
1049
|
-
//
|
|
1050
|
-
|
|
1279
|
+
// ── Discussions: close all open, create fresh one ─────────────────
|
|
1280
|
+
console.log("\n --- Discussions: close open, create fresh ---");
|
|
1051
1281
|
try {
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
const result = execSync(`gh api graphql --input -`, {
|
|
1056
|
-
cwd: target,
|
|
1057
|
-
encoding: "utf8",
|
|
1058
|
-
timeout: 30000,
|
|
1059
|
-
input: query,
|
|
1060
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1061
|
-
});
|
|
1062
|
-
const parsed = JSON.parse(result);
|
|
1282
|
+
const parsed = ghGraphQL(
|
|
1283
|
+
`{ repository(owner:"${owner}", name:"${repo}") { discussions(first:50, states:OPEN) { nodes { id number title } } } }`,
|
|
1284
|
+
);
|
|
1063
1285
|
const discussions = parsed?.data?.repository?.discussions?.nodes || [];
|
|
1064
1286
|
if (discussions.length === 0) {
|
|
1065
1287
|
console.log(" No open discussions to close");
|
|
@@ -1068,16 +1290,7 @@ function initPurgeGitHub() {
|
|
|
1068
1290
|
console.log(` CLOSE: discussion #${disc.number} — ${disc.title}`);
|
|
1069
1291
|
if (!dryRun) {
|
|
1070
1292
|
try {
|
|
1071
|
-
|
|
1072
|
-
query: `mutation { closeDiscussion(input: { discussionId: "${disc.id}" }) { discussion { number } } }`,
|
|
1073
|
-
});
|
|
1074
|
-
execSync(`gh api graphql --input -`, {
|
|
1075
|
-
cwd: target,
|
|
1076
|
-
encoding: "utf8",
|
|
1077
|
-
timeout: 15000,
|
|
1078
|
-
input: mutation,
|
|
1079
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1080
|
-
});
|
|
1293
|
+
ghGraphQL(`mutation { closeDiscussion(input: { discussionId: "${disc.id}" }) { discussion { number } } }`);
|
|
1081
1294
|
initChanges++;
|
|
1082
1295
|
} catch {
|
|
1083
1296
|
console.log(` SKIP: Could not close discussion #${disc.number} (may need admin permissions)`);
|
|
@@ -1093,18 +1306,9 @@ function initPurgeGitHub() {
|
|
|
1093
1306
|
|
|
1094
1307
|
// Create a new "Talk to the repository" discussion
|
|
1095
1308
|
try {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
});
|
|
1100
|
-
const repoResult = execSync(`gh api graphql --input -`, {
|
|
1101
|
-
cwd: target,
|
|
1102
|
-
encoding: "utf8",
|
|
1103
|
-
timeout: 30000,
|
|
1104
|
-
input: repoQuery,
|
|
1105
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1106
|
-
});
|
|
1107
|
-
const repoParsed = JSON.parse(repoResult);
|
|
1309
|
+
const repoParsed = ghGraphQL(
|
|
1310
|
+
`{ repository(owner:"${owner}", name:"${repo}") { id discussionCategories(first:20) { nodes { id name } } } }`,
|
|
1311
|
+
);
|
|
1108
1312
|
const repoId = repoParsed?.data?.repository?.id;
|
|
1109
1313
|
const categories = repoParsed?.data?.repository?.discussionCategories?.nodes || [];
|
|
1110
1314
|
const generalCat = categories.find((c) => c.name === "General");
|
|
@@ -1113,17 +1317,9 @@ function initPurgeGitHub() {
|
|
|
1113
1317
|
} else {
|
|
1114
1318
|
console.log(' CREATE: discussion "Talk to the repository" in General category');
|
|
1115
1319
|
if (!dryRun) {
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
const createResult = execSync(`gh api graphql --input -`, {
|
|
1120
|
-
cwd: target,
|
|
1121
|
-
encoding: "utf8",
|
|
1122
|
-
timeout: 15000,
|
|
1123
|
-
input: createMutation,
|
|
1124
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1125
|
-
});
|
|
1126
|
-
const createParsed = JSON.parse(createResult);
|
|
1320
|
+
const createParsed = ghGraphQL(
|
|
1321
|
+
`mutation { createDiscussion(input: { repositoryId: "${repoId}", categoryId: "${generalCat.id}", title: "Talk to the repository", body: "This discussion is the main channel for interacting with the repository's autonomous agents.\\n\\nUse this thread to:\\n- Submit feature requests or ideas\\n- Ask questions about the project\\n- Chat with the discussions bot\\n\\n---\\n*Created by init --purge*" }) { discussion { number url } } }`,
|
|
1322
|
+
);
|
|
1127
1323
|
const newDisc = createParsed?.data?.createDiscussion?.discussion;
|
|
1128
1324
|
if (newDisc) {
|
|
1129
1325
|
console.log(` CREATED: discussion #${newDisc.number} — ${newDisc.url}`);
|
|
@@ -1137,7 +1333,7 @@ function initPurgeGitHub() {
|
|
|
1137
1333
|
console.log(` SKIP: Could not create discussion (${err.message})`);
|
|
1138
1334
|
}
|
|
1139
1335
|
|
|
1140
|
-
// Enable GitHub Pages
|
|
1336
|
+
// ── Enable GitHub Pages ───────────────────────────────────────────
|
|
1141
1337
|
console.log("\n--- Enable GitHub Pages ---");
|
|
1142
1338
|
try {
|
|
1143
1339
|
if (!dryRun) {
|
|
@@ -1187,14 +1383,31 @@ function runInit() {
|
|
|
1187
1383
|
console.log(`Mode: ${dryRun ? "DRY RUN" : "LIVE"}`);
|
|
1188
1384
|
console.log("");
|
|
1189
1385
|
|
|
1386
|
+
// Capture existing init timestamp before any destructive operations (for idempotency).
|
|
1387
|
+
// The TOML [init] section is the authoritative record. If it matches the current
|
|
1388
|
+
// mode/mission/version, reuse its timestamp so that re-running init is a no-op.
|
|
1389
|
+
const pkg = JSON.parse(readFileSync(resolve(pkgRoot, "package.json"), "utf8"));
|
|
1390
|
+
let initTimestamp = null;
|
|
1391
|
+
const tomlPath = resolve(target, "agentic-lib.toml");
|
|
1392
|
+
if (existsSync(tomlPath)) {
|
|
1393
|
+
const tomlContent = readFileSync(tomlPath, "utf8");
|
|
1394
|
+
const tm = tomlContent.match(/^\[init\]\s*\ntimestamp\s*=\s*"([^"]+)"\s*\nmode\s*=\s*"([^"]+)"\s*\nmission\s*=\s*"([^"]+)"\s*\nversion\s*=\s*"([^"]+)"/m);
|
|
1395
|
+
const mode = purge ? "purge" : reseed ? "reseed" : null;
|
|
1396
|
+
if (tm && mode && tm[2] === mode && tm[3] === mission && tm[4] === pkg.version) {
|
|
1397
|
+
initTimestamp = tm[1];
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
// Use a single timestamp for the entire init run (for consistency across files)
|
|
1401
|
+
if (!initTimestamp) initTimestamp = new Date().toISOString();
|
|
1402
|
+
|
|
1190
1403
|
initWorkflows();
|
|
1191
1404
|
initActions(agenticDir);
|
|
1192
1405
|
initDirContents("agents", resolve(agenticDir, "agents"), "Agents");
|
|
1193
1406
|
initDirContents("seeds", resolve(agenticDir, "seeds"), "Seeds");
|
|
1194
1407
|
initScripts(agenticDir);
|
|
1195
1408
|
initConfig(seedsDir);
|
|
1196
|
-
if (reseed) initReseed();
|
|
1197
|
-
if (purge) initPurge(seedsDir, mission);
|
|
1409
|
+
if (reseed) initReseed(initTimestamp);
|
|
1410
|
+
if (purge) initPurge(seedsDir, mission, initTimestamp);
|
|
1198
1411
|
if (purge) initPurgeGitHub();
|
|
1199
1412
|
|
|
1200
1413
|
console.log(`\n${initChanges} change(s)${dryRun ? " (dry run)" : ""}`);
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ import * as core from "@actions/core";
|
|
|
9
9
|
import * as github from "@actions/github";
|
|
10
10
|
import { loadConfig, getWritablePaths } from "./config-loader.js";
|
|
11
11
|
import { logActivity, generateClosingNotes } from "./logging.js";
|
|
12
|
-
import { readFileSync } from "fs";
|
|
12
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
13
13
|
|
|
14
14
|
// Task implementations
|
|
15
15
|
import { resolveIssue } from "./tasks/resolve-issue.js";
|
|
@@ -99,15 +99,63 @@ async function run() {
|
|
|
99
99
|
if (result.action) core.setOutput("action", result.action);
|
|
100
100
|
if (result.actionArg) core.setOutput("action-arg", result.actionArg);
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
const profileName = config.tuning?.profileName || "unknown";
|
|
103
|
+
|
|
104
|
+
// Transformation cost: 1 for code-changing tasks, 0 otherwise
|
|
105
|
+
const COST_TASKS = ["transform", "fix-code", "maintain-features", "maintain-library"];
|
|
106
|
+
const isNop = result.outcome === "nop" || result.outcome === "error";
|
|
107
|
+
const transformationCost = COST_TASKS.includes(task) && !isNop ? 1 : 0;
|
|
108
|
+
|
|
109
|
+
// Read cumulative transformation cost from the activity log
|
|
110
|
+
const intentionFilepath = config.intentionBot?.intentionFilepath;
|
|
111
|
+
let cumulativeCost = 0;
|
|
112
|
+
if (intentionFilepath && existsSync(intentionFilepath)) {
|
|
113
|
+
const logContent = readFileSync(intentionFilepath, "utf8");
|
|
114
|
+
const costMatches = logContent.matchAll(/\*\*agentic-lib transformation cost:\*\* (\d+)/g);
|
|
115
|
+
cumulativeCost = [...costMatches].reduce((sum, m) => sum + parseInt(m[1], 10), 0);
|
|
116
|
+
}
|
|
117
|
+
cumulativeCost += transformationCost;
|
|
118
|
+
|
|
119
|
+
// Count features and library docs on disk
|
|
120
|
+
const featuresPath = config.paths?.features?.path;
|
|
121
|
+
const featuresUsed = featuresPath && existsSync(featuresPath)
|
|
122
|
+
? readdirSync(featuresPath).filter((f) => f.endsWith(".md")).length
|
|
123
|
+
: 0;
|
|
124
|
+
const libraryPath = config.paths?.library?.path;
|
|
125
|
+
const libraryUsed = libraryPath && existsSync(libraryPath)
|
|
126
|
+
? readdirSync(libraryPath).filter((f) => f.endsWith(".md")).length
|
|
127
|
+
: 0;
|
|
128
|
+
|
|
129
|
+
// Count open automated issues (feature vs maintenance)
|
|
130
|
+
let featureIssueCount = 0;
|
|
131
|
+
let maintenanceIssueCount = 0;
|
|
132
|
+
try {
|
|
133
|
+
const { data: openAutoIssues } = await context.octokit.rest.issues.listForRepo({
|
|
134
|
+
...context.repo,
|
|
135
|
+
state: "open",
|
|
136
|
+
labels: "automated",
|
|
137
|
+
per_page: 50,
|
|
138
|
+
});
|
|
139
|
+
for (const oi of openAutoIssues.filter((i) => !i.pull_request)) {
|
|
140
|
+
const lbls = oi.labels.map((l) => l.name);
|
|
141
|
+
if (lbls.includes("maintenance")) maintenanceIssueCount++;
|
|
142
|
+
else featureIssueCount++;
|
|
143
|
+
}
|
|
144
|
+
} catch (_) { /* API not available */ }
|
|
145
|
+
|
|
146
|
+
const budgetCap = config.transformationBudget || 0;
|
|
147
|
+
const featCap = config.paths?.features?.limit || 4;
|
|
148
|
+
const libCap = config.paths?.library?.limit || 32;
|
|
149
|
+
|
|
150
|
+
// Compute limits status with actual values
|
|
103
151
|
const limitsStatus = [
|
|
104
|
-
{ name: "transformation-budget", valueNum:
|
|
105
|
-
{ name: "max-feature-issues", valueNum:
|
|
106
|
-
{ name: "max-maintenance-issues", valueNum:
|
|
152
|
+
{ name: "transformation-budget", valueNum: cumulativeCost, capacityNum: budgetCap, value: `${cumulativeCost}/${budgetCap}`, remaining: `${Math.max(0, budgetCap - cumulativeCost)}`, status: cumulativeCost >= budgetCap && budgetCap > 0 ? "EXHAUSTED" : "" },
|
|
153
|
+
{ name: "max-feature-issues", valueNum: featureIssueCount, capacityNum: config.featureDevelopmentIssuesWipLimit, value: `${featureIssueCount}/${config.featureDevelopmentIssuesWipLimit}`, remaining: `${Math.max(0, config.featureDevelopmentIssuesWipLimit - featureIssueCount)}`, status: "" },
|
|
154
|
+
{ name: "max-maintenance-issues", valueNum: maintenanceIssueCount, capacityNum: config.maintenanceIssuesWipLimit, value: `${maintenanceIssueCount}/${config.maintenanceIssuesWipLimit}`, remaining: `${Math.max(0, config.maintenanceIssuesWipLimit - maintenanceIssueCount)}`, status: "" },
|
|
107
155
|
{ name: "max-attempts-per-issue", valueNum: 0, capacityNum: config.attemptsPerIssue, value: `?/${config.attemptsPerIssue}`, remaining: "?", status: task === "resolve-issue" ? "" : "n/a" },
|
|
108
156
|
{ name: "max-attempts-per-branch", valueNum: 0, capacityNum: config.attemptsPerBranch, value: `?/${config.attemptsPerBranch}`, remaining: "?", status: task === "fix-code" ? "" : "n/a" },
|
|
109
|
-
{ name: "features", valueNum:
|
|
110
|
-
{ name: "library", valueNum:
|
|
157
|
+
{ name: "features", valueNum: featuresUsed, capacityNum: featCap, value: `${featuresUsed}/${featCap}`, remaining: `${Math.max(0, featCap - featuresUsed)}`, status: ["maintain-features", "transform"].includes(task) ? "" : "n/a" },
|
|
158
|
+
{ name: "library", valueNum: libraryUsed, capacityNum: libCap, value: `${libraryUsed}/${libCap}`, remaining: `${Math.max(0, libCap - libraryUsed)}`, status: task === "maintain-library" ? "" : "n/a" },
|
|
111
159
|
];
|
|
112
160
|
|
|
113
161
|
// Merge task-reported limits if available
|
|
@@ -119,15 +167,8 @@ async function run() {
|
|
|
119
167
|
}
|
|
120
168
|
|
|
121
169
|
const closingNotes = result.closingNotes || generateClosingNotes(limitsStatus);
|
|
122
|
-
const profileName = config.tuning?.profileName || "unknown";
|
|
123
|
-
|
|
124
|
-
// Transformation cost: 1 for code-changing tasks, 0 otherwise
|
|
125
|
-
const COST_TASKS = ["transform", "fix-code", "maintain-features", "maintain-library"];
|
|
126
|
-
const isNop = result.outcome === "nop" || result.outcome === "error";
|
|
127
|
-
const transformationCost = COST_TASKS.includes(task) && !isNop ? 1 : 0;
|
|
128
170
|
|
|
129
171
|
// Log to intentïon.md (commit-if-changed excludes this on non-default branches)
|
|
130
|
-
const intentionFilepath = config.intentionBot?.intentionFilepath;
|
|
131
172
|
if (intentionFilepath) {
|
|
132
173
|
logActivity({
|
|
133
174
|
filepath: intentionFilepath,
|
|
@@ -53,7 +53,7 @@ async function fetchDiscussion(octokit, discussionUrl, commentsLimit = 10) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function buildPrompt(discussionUrl, discussion, context, t) {
|
|
56
|
+
function buildPrompt(discussionUrl, discussion, context, t, repoContext) {
|
|
57
57
|
const { config, instructions } = context;
|
|
58
58
|
const { title, body, comments } = discussion;
|
|
59
59
|
|
|
@@ -99,6 +99,28 @@ function buildPrompt(discussionUrl, discussion, context, t) {
|
|
|
99
99
|
`### Mission\n${mission}`,
|
|
100
100
|
contributing ? `### Contributing\n${contributing}` : "",
|
|
101
101
|
`### Current Features\n${featureNames.join(", ") || "none"}`,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Add issue context
|
|
105
|
+
if (repoContext?.issuesSummary?.length > 0) {
|
|
106
|
+
parts.push(`### Open Issues (${repoContext.issuesSummary.length})`, repoContext.issuesSummary.join("\n"));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add actions-since-init context
|
|
110
|
+
if (repoContext?.actionsSinceInit?.length > 0) {
|
|
111
|
+
parts.push(
|
|
112
|
+
`### Actions Since Last Init${repoContext.initTimestamp ? ` (${repoContext.initTimestamp})` : ""}`,
|
|
113
|
+
...repoContext.actionsSinceInit.map((a) => {
|
|
114
|
+
let line = `- ${a.name}: ${a.conclusion} (${a.created}) [${a.commitSha}] ${a.commitMessage}`;
|
|
115
|
+
if (a.prNumber) {
|
|
116
|
+
line += ` — PR #${a.prNumber}: +${a.additions}/-${a.deletions} in ${a.changedFiles} file(s)`;
|
|
117
|
+
}
|
|
118
|
+
return line;
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
parts.push(
|
|
102
124
|
recentActivity ? `### Recent Activity\n${recentActivity}` : "",
|
|
103
125
|
config.configToml ? `### Configuration (agentic-lib.toml)\n\`\`\`toml\n${config.configToml}\n\`\`\`` : "",
|
|
104
126
|
config.packageJson ? `### Dependencies (package.json)\n\`\`\`json\n${config.packageJson}\n\`\`\`` : "",
|
|
@@ -151,15 +173,72 @@ async function postReply(octokit, nodeId, replyBody) {
|
|
|
151
173
|
* @returns {Promise<Object>} Result with outcome, action, tokensUsed, model
|
|
152
174
|
*/
|
|
153
175
|
export async function discussions(context) {
|
|
154
|
-
const { octokit, model, discussionUrl } = context;
|
|
155
|
-
const t =
|
|
176
|
+
const { octokit, model, discussionUrl, repo, config } = context;
|
|
177
|
+
const t = config?.tuning || {};
|
|
156
178
|
|
|
157
179
|
if (!discussionUrl) {
|
|
158
180
|
throw new Error("discussions task requires discussion-url input");
|
|
159
181
|
}
|
|
160
182
|
|
|
183
|
+
// Gather repo context: issues + actions since init
|
|
184
|
+
const repoContext = { issuesSummary: [], actionsSinceInit: [], initTimestamp: null };
|
|
185
|
+
if (octokit && repo) {
|
|
186
|
+
try {
|
|
187
|
+
const { data: openIssues } = await octokit.rest.issues.listForRepo({
|
|
188
|
+
...repo, state: "open", per_page: 10, sort: "created", direction: "asc",
|
|
189
|
+
});
|
|
190
|
+
repoContext.issuesSummary = openIssues
|
|
191
|
+
.filter((i) => !i.pull_request)
|
|
192
|
+
.map((i) => {
|
|
193
|
+
const labels = i.labels.map((l) => l.name).join(", ");
|
|
194
|
+
return `#${i.number}: ${i.title} [${labels || "no labels"}]`;
|
|
195
|
+
});
|
|
196
|
+
} catch (err) {
|
|
197
|
+
core.warning(`Could not fetch issues for discussion context: ${err.message}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const initTimestamp = config?.init?.timestamp || null;
|
|
201
|
+
repoContext.initTimestamp = initTimestamp;
|
|
202
|
+
try {
|
|
203
|
+
const { data: runs } = await octokit.rest.actions.listWorkflowRunsForRepo({
|
|
204
|
+
...repo, per_page: 20,
|
|
205
|
+
});
|
|
206
|
+
const initDate = initTimestamp ? new Date(initTimestamp) : null;
|
|
207
|
+
const relevantRuns = initDate
|
|
208
|
+
? runs.workflow_runs.filter((r) => new Date(r.created_at) >= initDate)
|
|
209
|
+
: runs.workflow_runs.slice(0, 10);
|
|
210
|
+
|
|
211
|
+
for (const run of relevantRuns) {
|
|
212
|
+
const commit = run.head_commit;
|
|
213
|
+
const entry = {
|
|
214
|
+
name: run.name,
|
|
215
|
+
conclusion: run.conclusion || run.status,
|
|
216
|
+
created: run.created_at,
|
|
217
|
+
commitMessage: commit?.message?.split("\n")[0] || "",
|
|
218
|
+
commitSha: run.head_sha?.substring(0, 7) || "",
|
|
219
|
+
};
|
|
220
|
+
if (run.head_branch?.startsWith("agentic-lib-issue-")) {
|
|
221
|
+
try {
|
|
222
|
+
const { data: prs } = await octokit.rest.pulls.list({
|
|
223
|
+
...repo, head: `${repo.owner}:${run.head_branch}`, state: "all", per_page: 1,
|
|
224
|
+
});
|
|
225
|
+
if (prs.length > 0) {
|
|
226
|
+
entry.prNumber = prs[0].number;
|
|
227
|
+
entry.additions = prs[0].additions;
|
|
228
|
+
entry.deletions = prs[0].deletions;
|
|
229
|
+
entry.changedFiles = prs[0].changed_files;
|
|
230
|
+
}
|
|
231
|
+
} catch { /* ignore */ }
|
|
232
|
+
}
|
|
233
|
+
repoContext.actionsSinceInit.push(entry);
|
|
234
|
+
}
|
|
235
|
+
} catch (err) {
|
|
236
|
+
core.warning(`Could not fetch workflow runs for discussion context: ${err.message}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
161
240
|
const discussion = await fetchDiscussion(octokit, discussionUrl, t.discussionComments || 10);
|
|
162
|
-
const prompt = buildPrompt(discussionUrl, discussion, context, t);
|
|
241
|
+
const prompt = buildPrompt(discussionUrl, discussion, context, t, repoContext);
|
|
163
242
|
const { content, tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
|
|
164
243
|
model,
|
|
165
244
|
systemMessage:
|
|
@@ -191,6 +270,61 @@ export async function discussions(context) {
|
|
|
191
270
|
core.info("Mission complete signal written (MISSION_COMPLETE.md)");
|
|
192
271
|
}
|
|
193
272
|
|
|
273
|
+
// Create issue when bot requests it
|
|
274
|
+
if (action === "create-issue" && actionArg) {
|
|
275
|
+
try {
|
|
276
|
+
const { data: issue } = await octokit.rest.issues.create({
|
|
277
|
+
...context.repo,
|
|
278
|
+
title: actionArg,
|
|
279
|
+
labels: ["automated", "enhancement"],
|
|
280
|
+
});
|
|
281
|
+
core.info(`Created issue #${issue.number}: ${actionArg}`);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
core.warning(`Failed to create issue: ${err.message}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Guard: never dispatch workflows from the SDK repo itself (agentic-lib)
|
|
288
|
+
const isSdkRepo = process.env.GITHUB_REPOSITORY === "xn-intenton-z2a/agentic-lib";
|
|
289
|
+
|
|
290
|
+
// Request supervisor evaluation
|
|
291
|
+
if (action === "request-supervisor") {
|
|
292
|
+
if (isSdkRepo) {
|
|
293
|
+
core.info("Skipping supervisor dispatch — running in SDK repo");
|
|
294
|
+
} else {
|
|
295
|
+
try {
|
|
296
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
297
|
+
...context.repo,
|
|
298
|
+
workflow_id: "agentic-lib-workflow.yml",
|
|
299
|
+
ref: "main",
|
|
300
|
+
inputs: { message: actionArg || "Discussion bot referral" },
|
|
301
|
+
});
|
|
302
|
+
core.info(`Dispatched supervisor with message: ${actionArg}`);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
core.warning(`Failed to dispatch supervisor: ${err.message}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Stop automation
|
|
310
|
+
if (action === "stop") {
|
|
311
|
+
if (isSdkRepo) {
|
|
312
|
+
core.info("Skipping schedule dispatch — running in SDK repo");
|
|
313
|
+
} else {
|
|
314
|
+
try {
|
|
315
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
316
|
+
...context.repo,
|
|
317
|
+
workflow_id: "agentic-lib-schedule.yml",
|
|
318
|
+
ref: "main",
|
|
319
|
+
inputs: { frequency: "off" },
|
|
320
|
+
});
|
|
321
|
+
core.info("Automation stopped via discussions bot");
|
|
322
|
+
} catch (err) {
|
|
323
|
+
core.warning(`Failed to stop automation: ${err.message}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
194
328
|
await postReply(octokit, discussion.nodeId, replyBody);
|
|
195
329
|
|
|
196
330
|
const argSuffix = actionArg ? ` (${actionArg})` : "";
|
|
@@ -60,6 +60,24 @@ async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, ins
|
|
|
60
60
|
|
|
61
61
|
const agentInstructions = instructions || "Review whether this issue has been resolved by the current codebase.";
|
|
62
62
|
|
|
63
|
+
// Gather recent commits since init for context
|
|
64
|
+
let recentCommitsSummary = [];
|
|
65
|
+
try {
|
|
66
|
+
const initTimestamp = config.init?.timestamp || null;
|
|
67
|
+
const since = initTimestamp || new Date(Date.now() - 7 * 86400000).toISOString();
|
|
68
|
+
const { data: commits } = await octokit.rest.repos.listCommits({
|
|
69
|
+
...repo,
|
|
70
|
+
sha: "main",
|
|
71
|
+
since,
|
|
72
|
+
per_page: 10,
|
|
73
|
+
});
|
|
74
|
+
recentCommitsSummary = commits.map((c) => {
|
|
75
|
+
const msg = c.commit.message.split("\n")[0];
|
|
76
|
+
const sha = c.sha.substring(0, 7);
|
|
77
|
+
return `- [${sha}] ${msg} (${c.commit.author?.date || ""})`;
|
|
78
|
+
});
|
|
79
|
+
} catch { /* ignore */ }
|
|
80
|
+
|
|
63
81
|
const prompt = [
|
|
64
82
|
"## Instructions",
|
|
65
83
|
agentInstructions,
|
|
@@ -84,6 +102,9 @@ async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, ins
|
|
|
84
102
|
...(docsFiles.length > 0
|
|
85
103
|
? [`## Documentation (${docsFiles.length} files)`, ...docsFiles.map((f) => `- ${f.name}`), ""]
|
|
86
104
|
: []),
|
|
105
|
+
...(recentCommitsSummary.length > 0
|
|
106
|
+
? [`## Recent Commits (since init)`, ...recentCommitsSummary, ""]
|
|
107
|
+
: []),
|
|
87
108
|
config.configToml ? `## Configuration (agentic-lib.toml)\n\`\`\`toml\n${config.configToml}\n\`\`\`` : "",
|
|
88
109
|
config.packageJson ? `## Dependencies (package.json)\n\`\`\`json\n${config.packageJson}\n\`\`\`` : "",
|
|
89
110
|
"",
|
|
@@ -6,12 +6,17 @@
|
|
|
6
6
|
// asks the Copilot SDK to choose multiple concurrent actions, then dispatches them.
|
|
7
7
|
|
|
8
8
|
import * as core from "@actions/core";
|
|
9
|
-
import { existsSync, readFileSync } from "fs";
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
10
10
|
import { runCopilotTask, readOptionalFile, scanDirectory, filterIssues } from "../copilot.js";
|
|
11
11
|
|
|
12
12
|
async function gatherContext(octokit, repo, config, t) {
|
|
13
13
|
const mission = readOptionalFile(config.paths.mission.path);
|
|
14
|
-
const
|
|
14
|
+
const intentionLogFull = readOptionalFile(config.intentionBot.intentionFilepath);
|
|
15
|
+
const recentActivity = intentionLogFull.split("\n").slice(-20).join("\n");
|
|
16
|
+
|
|
17
|
+
// Read cumulative transformation cost from the activity log
|
|
18
|
+
const costMatches = intentionLogFull.matchAll(/\*\*agentic-lib transformation cost:\*\* (\d+)/g);
|
|
19
|
+
const cumulativeTransformationCost = [...costMatches].reduce((sum, m) => sum + parseInt(m[1], 10), 0);
|
|
15
20
|
|
|
16
21
|
// Check mission-complete signal
|
|
17
22
|
const missionComplete = existsSync("MISSION_COMPLETE.md");
|
|
@@ -20,6 +25,13 @@ async function gatherContext(octokit, repo, config, t) {
|
|
|
20
25
|
missionCompleteInfo = readFileSync("MISSION_COMPLETE.md", "utf8").substring(0, 500);
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
// Check mission-failed signal
|
|
29
|
+
const missionFailed = existsSync("MISSION_FAILED.md");
|
|
30
|
+
let missionFailedInfo = "";
|
|
31
|
+
if (missionFailed) {
|
|
32
|
+
missionFailedInfo = readFileSync("MISSION_FAILED.md", "utf8").substring(0, 500);
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
// Check transformation budget
|
|
24
36
|
const transformationBudget = config.transformationBudget || 0;
|
|
25
37
|
|
|
@@ -55,6 +67,36 @@ async function gatherContext(octokit, repo, config, t) {
|
|
|
55
67
|
return `#${i.number}: ${i.title} [${labels || "no labels"}] (${age}d old)`;
|
|
56
68
|
});
|
|
57
69
|
|
|
70
|
+
// Fetch recently-closed issues for mission-complete detection and dedup
|
|
71
|
+
let recentlyClosedSummary = [];
|
|
72
|
+
try {
|
|
73
|
+
const { data: closedIssuesRaw } = await octokit.rest.issues.listForRepo({
|
|
74
|
+
...repo,
|
|
75
|
+
state: "closed",
|
|
76
|
+
per_page: 5,
|
|
77
|
+
sort: "updated",
|
|
78
|
+
direction: "desc",
|
|
79
|
+
});
|
|
80
|
+
for (const ci of closedIssuesRaw.filter((i) => !i.pull_request)) {
|
|
81
|
+
let closeReason = "closed";
|
|
82
|
+
try {
|
|
83
|
+
const { data: comments } = await octokit.rest.issues.listComments({
|
|
84
|
+
...repo,
|
|
85
|
+
issue_number: ci.number,
|
|
86
|
+
per_page: 1,
|
|
87
|
+
sort: "created",
|
|
88
|
+
direction: "desc",
|
|
89
|
+
});
|
|
90
|
+
if (comments.length > 0 && comments[0].body?.includes("Automated Review Result")) {
|
|
91
|
+
closeReason = "closed by review as RESOLVED";
|
|
92
|
+
}
|
|
93
|
+
} catch (_) { /* ignore */ }
|
|
94
|
+
recentlyClosedSummary.push(`#${ci.number}: ${ci.title} — ${closeReason}`);
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
core.warning(`Could not fetch recently closed issues: ${err.message}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
58
100
|
const { data: openPRs } = await octokit.rest.pulls.list({
|
|
59
101
|
...repo,
|
|
60
102
|
state: "open",
|
|
@@ -68,13 +110,55 @@ async function gatherContext(octokit, repo, config, t) {
|
|
|
68
110
|
return `#${pr.number}: ${pr.title} (${pr.head.ref}) [${labels || "no labels"}] (${age}d old)`;
|
|
69
111
|
});
|
|
70
112
|
|
|
113
|
+
// Read init timestamp for epoch boundary
|
|
114
|
+
const initTimestamp = config.init?.timestamp || null;
|
|
115
|
+
|
|
71
116
|
let workflowsSummary = [];
|
|
117
|
+
let actionsSinceInit = [];
|
|
72
118
|
try {
|
|
73
119
|
const { data: runs } = await octokit.rest.actions.listWorkflowRunsForRepo({
|
|
74
120
|
...repo,
|
|
75
|
-
per_page:
|
|
121
|
+
per_page: 20,
|
|
76
122
|
});
|
|
77
123
|
workflowsSummary = runs.workflow_runs.map((r) => `${r.name}: ${r.conclusion || r.status} (${r.created_at})`);
|
|
124
|
+
|
|
125
|
+
// Build detailed actions-since-init with commit context
|
|
126
|
+
const initDate = initTimestamp ? new Date(initTimestamp) : null;
|
|
127
|
+
const relevantRuns = initDate
|
|
128
|
+
? runs.workflow_runs.filter((r) => new Date(r.created_at) >= initDate)
|
|
129
|
+
: runs.workflow_runs.slice(0, 10);
|
|
130
|
+
|
|
131
|
+
for (const run of relevantRuns) {
|
|
132
|
+
const commit = run.head_commit;
|
|
133
|
+
const entry = {
|
|
134
|
+
name: run.name,
|
|
135
|
+
conclusion: run.conclusion || run.status,
|
|
136
|
+
created: run.created_at,
|
|
137
|
+
commitMessage: commit?.message?.split("\n")[0] || "",
|
|
138
|
+
commitSha: run.head_sha?.substring(0, 7) || "",
|
|
139
|
+
branch: run.head_branch || "",
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// For transform branches, try to get PR change stats
|
|
143
|
+
if (run.head_branch?.startsWith("agentic-lib-issue-")) {
|
|
144
|
+
try {
|
|
145
|
+
const { data: prs } = await octokit.rest.pulls.list({
|
|
146
|
+
...repo,
|
|
147
|
+
head: `${repo.owner}:${run.head_branch}`,
|
|
148
|
+
state: "all",
|
|
149
|
+
per_page: 1,
|
|
150
|
+
});
|
|
151
|
+
if (prs.length > 0) {
|
|
152
|
+
entry.prNumber = prs[0].number;
|
|
153
|
+
entry.prTitle = prs[0].title;
|
|
154
|
+
entry.additions = prs[0].additions;
|
|
155
|
+
entry.deletions = prs[0].deletions;
|
|
156
|
+
entry.changedFiles = prs[0].changed_files;
|
|
157
|
+
}
|
|
158
|
+
} catch { /* ignore */ }
|
|
159
|
+
}
|
|
160
|
+
actionsSinceInit.push(entry);
|
|
161
|
+
}
|
|
78
162
|
} catch (err) {
|
|
79
163
|
core.warning(`Could not fetch workflow runs: ${err.message}`);
|
|
80
164
|
}
|
|
@@ -90,6 +174,8 @@ async function gatherContext(octokit, repo, config, t) {
|
|
|
90
174
|
oldestReadyIssue,
|
|
91
175
|
prsSummary,
|
|
92
176
|
workflowsSummary,
|
|
177
|
+
actionsSinceInit,
|
|
178
|
+
initTimestamp,
|
|
93
179
|
supervisor: config.supervisor,
|
|
94
180
|
configToml: config.configToml,
|
|
95
181
|
packageJson: config.packageJson,
|
|
@@ -98,7 +184,11 @@ async function gatherContext(octokit, repo, config, t) {
|
|
|
98
184
|
activeDiscussionUrl,
|
|
99
185
|
missionComplete,
|
|
100
186
|
missionCompleteInfo,
|
|
187
|
+
missionFailed,
|
|
188
|
+
missionFailedInfo,
|
|
101
189
|
transformationBudget,
|
|
190
|
+
cumulativeTransformationCost,
|
|
191
|
+
recentlyClosedSummary,
|
|
102
192
|
};
|
|
103
193
|
}
|
|
104
194
|
|
|
@@ -114,6 +204,9 @@ function buildPrompt(ctx, agentInstructions) {
|
|
|
114
204
|
`### Open Issues (${ctx.issuesSummary.length})`,
|
|
115
205
|
ctx.issuesSummary.join("\n") || "none",
|
|
116
206
|
"",
|
|
207
|
+
`### Recently Closed Issues (${ctx.recentlyClosedSummary.length})`,
|
|
208
|
+
ctx.recentlyClosedSummary.join("\n") || "none",
|
|
209
|
+
"",
|
|
117
210
|
`### Open PRs (${ctx.prsSummary.length})`,
|
|
118
211
|
ctx.prsSummary.join("\n") || "none",
|
|
119
212
|
"",
|
|
@@ -126,6 +219,20 @@ function buildPrompt(ctx, agentInstructions) {
|
|
|
126
219
|
`### Recent Workflow Runs`,
|
|
127
220
|
ctx.workflowsSummary.join("\n") || "none",
|
|
128
221
|
"",
|
|
222
|
+
...(ctx.actionsSinceInit.length > 0
|
|
223
|
+
? [
|
|
224
|
+
`### Actions Since Last Init${ctx.initTimestamp ? ` (${ctx.initTimestamp})` : ""}`,
|
|
225
|
+
"Each entry: workflow | outcome | commit | branch | changes",
|
|
226
|
+
...ctx.actionsSinceInit.map((a) => {
|
|
227
|
+
let line = `- ${a.name}: ${a.conclusion} (${a.created}) [${a.commitSha}] ${a.commitMessage}`;
|
|
228
|
+
if (a.prNumber) {
|
|
229
|
+
line += ` — PR #${a.prNumber}: +${a.additions}/-${a.deletions} in ${a.changedFiles} file(s)`;
|
|
230
|
+
}
|
|
231
|
+
return line;
|
|
232
|
+
}),
|
|
233
|
+
"",
|
|
234
|
+
]
|
|
235
|
+
: []),
|
|
129
236
|
`### Recent Activity`,
|
|
130
237
|
ctx.recentActivity || "none",
|
|
131
238
|
"",
|
|
@@ -150,8 +257,17 @@ function buildPrompt(ctx, agentInstructions) {
|
|
|
150
257
|
"",
|
|
151
258
|
]
|
|
152
259
|
: []),
|
|
260
|
+
...(ctx.missionFailed
|
|
261
|
+
? [
|
|
262
|
+
`### Mission Status: FAILED`,
|
|
263
|
+
ctx.missionFailedInfo,
|
|
264
|
+
"The mission has been declared failed. The schedule should be set to off.",
|
|
265
|
+
"You may still: review/close issues, respond to discussions.",
|
|
266
|
+
"",
|
|
267
|
+
]
|
|
268
|
+
: []),
|
|
153
269
|
...(ctx.transformationBudget > 0
|
|
154
|
-
? [`### Transformation Budget: ${ctx.transformationBudget}
|
|
270
|
+
? [`### Transformation Budget: ${ctx.cumulativeTransformationCost}/${ctx.transformationBudget} used (${Math.max(0, ctx.transformationBudget - ctx.cumulativeTransformationCost)} remaining)`, ""]
|
|
155
271
|
: []),
|
|
156
272
|
`### Issue Limits`,
|
|
157
273
|
`Feature development WIP limit: ${ctx.featureIssuesWipLimit}`,
|
|
@@ -176,6 +292,10 @@ function buildPrompt(ctx, agentInstructions) {
|
|
|
176
292
|
"### Communication",
|
|
177
293
|
"- `respond:discussions | message: <text> | discussion-url: <url>` — Reply via discussions bot",
|
|
178
294
|
"",
|
|
295
|
+
"### Mission Lifecycle",
|
|
296
|
+
"- `mission-complete | reason: <text>` — Declare mission accomplished. Writes MISSION_COMPLETE.md and sets schedule to off. Use when: all acceptance criteria in MISSION.md are satisfied, tests pass, and recently-closed issues confirm resolution.",
|
|
297
|
+
"- `mission-failed | reason: <text>` — Declare mission failed. Writes MISSION_FAILED.md and sets schedule to off. Use when: transformation budget is exhausted with no progress, pipeline is stuck in a loop, or the mission is unachievable.",
|
|
298
|
+
"",
|
|
179
299
|
"### Schedule Control",
|
|
180
300
|
"- `set-schedule:<frequency>` — Change supervisor schedule (off, weekly, daily, hourly, continuous). Use `set-schedule:weekly` when mission is substantially complete, `set-schedule:continuous` to ramp up.",
|
|
181
301
|
"",
|
|
@@ -227,6 +347,12 @@ async function executeDispatch(octokit, repo, actionName, params) {
|
|
|
227
347
|
if (params["pr-number"]) inputs["pr-number"] = params["pr-number"];
|
|
228
348
|
if (params["issue-number"]) inputs["issue-number"] = params["issue-number"];
|
|
229
349
|
|
|
350
|
+
// Guard: never dispatch workflows from the SDK repo itself (agentic-lib)
|
|
351
|
+
if (process.env.GITHUB_REPOSITORY === "xn-intenton-z2a/agentic-lib") {
|
|
352
|
+
core.info(`Skipping dispatch of ${workflowFile} — running in SDK repo`);
|
|
353
|
+
return `skipped:sdk-repo:${workflowFile}`;
|
|
354
|
+
}
|
|
355
|
+
|
|
230
356
|
// Guard: skip transform dispatch if one is already running
|
|
231
357
|
if (workflowFile === "agentic-lib-workflow.yml") {
|
|
232
358
|
try {
|
|
@@ -253,6 +379,31 @@ async function executeDispatch(octokit, repo, actionName, params) {
|
|
|
253
379
|
async function executeCreateIssue(octokit, repo, params) {
|
|
254
380
|
const title = params.title || "Untitled issue";
|
|
255
381
|
const labels = params.labels ? params.labels.split(",").map((l) => l.trim()) : ["automated"];
|
|
382
|
+
|
|
383
|
+
// Dedup guard: skip if a similarly-titled issue was closed in the last hour
|
|
384
|
+
try {
|
|
385
|
+
const { data: recent } = await octokit.rest.issues.listForRepo({
|
|
386
|
+
...repo,
|
|
387
|
+
state: "closed",
|
|
388
|
+
sort: "updated",
|
|
389
|
+
direction: "desc",
|
|
390
|
+
per_page: 5,
|
|
391
|
+
});
|
|
392
|
+
const titlePrefix = title.toLowerCase().substring(0, 30);
|
|
393
|
+
const duplicate = recent.find(
|
|
394
|
+
(i) =>
|
|
395
|
+
!i.pull_request &&
|
|
396
|
+
i.title.toLowerCase().includes(titlePrefix) &&
|
|
397
|
+
Date.now() - new Date(i.closed_at).getTime() < 3600000,
|
|
398
|
+
);
|
|
399
|
+
if (duplicate) {
|
|
400
|
+
core.info(`Skipping duplicate issue (similar to recently closed #${duplicate.number})`);
|
|
401
|
+
return `skipped:duplicate-of-#${duplicate.number}`;
|
|
402
|
+
}
|
|
403
|
+
} catch (err) {
|
|
404
|
+
core.warning(`Dedup check failed: ${err.message}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
256
407
|
core.info(`Creating issue: ${title}`);
|
|
257
408
|
const { data: issue } = await octokit.rest.issues.create({ ...repo, title, labels });
|
|
258
409
|
return `created-issue:#${issue.number}`;
|
|
@@ -283,6 +434,10 @@ async function executeRespondDiscussions(octokit, repo, params) {
|
|
|
283
434
|
const message = params.message || "";
|
|
284
435
|
const url = params["discussion-url"] || "";
|
|
285
436
|
if (message) {
|
|
437
|
+
if (process.env.GITHUB_REPOSITORY === "xn-intenton-z2a/agentic-lib") {
|
|
438
|
+
core.info("Skipping bot dispatch — running in SDK repo");
|
|
439
|
+
return `skipped:sdk-repo:respond-discussions`;
|
|
440
|
+
}
|
|
286
441
|
core.info(`Dispatching discussions bot with response: ${message.substring(0, 100)}`);
|
|
287
442
|
const inputs = { message };
|
|
288
443
|
if (url) inputs["discussion-url"] = url;
|
|
@@ -297,11 +452,69 @@ async function executeRespondDiscussions(octokit, repo, params) {
|
|
|
297
452
|
return "skipped:respond-no-message";
|
|
298
453
|
}
|
|
299
454
|
|
|
455
|
+
async function executeMissionComplete(octokit, repo, params) {
|
|
456
|
+
const reason = params.reason || "All acceptance criteria satisfied";
|
|
457
|
+
const signal = [
|
|
458
|
+
"# Mission Complete",
|
|
459
|
+
"",
|
|
460
|
+
`- **Timestamp:** ${new Date().toISOString()}`,
|
|
461
|
+
`- **Detected by:** supervisor`,
|
|
462
|
+
`- **Reason:** ${reason}`,
|
|
463
|
+
"",
|
|
464
|
+
"This file was created automatically. To restart transformations, delete this file or run `npx @xn-intenton-z2a/agentic-lib init --reseed`.",
|
|
465
|
+
].join("\n");
|
|
466
|
+
writeFileSync("MISSION_COMPLETE.md", signal);
|
|
467
|
+
core.info(`Mission complete signal written: ${reason}`);
|
|
468
|
+
if (process.env.GITHUB_REPOSITORY !== "xn-intenton-z2a/agentic-lib") {
|
|
469
|
+
try {
|
|
470
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
471
|
+
...repo,
|
|
472
|
+
workflow_id: "agentic-lib-schedule.yml",
|
|
473
|
+
ref: "main",
|
|
474
|
+
inputs: { frequency: "off" },
|
|
475
|
+
});
|
|
476
|
+
} catch (err) {
|
|
477
|
+
core.warning(`Could not set schedule to off: ${err.message}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return `mission-complete:${reason.substring(0, 100)}`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function executeMissionFailed(octokit, repo, params) {
|
|
484
|
+
const reason = params.reason || "Mission could not be completed";
|
|
485
|
+
const signal = [
|
|
486
|
+
"# Mission Failed",
|
|
487
|
+
"",
|
|
488
|
+
`- **Timestamp:** ${new Date().toISOString()}`,
|
|
489
|
+
`- **Detected by:** supervisor`,
|
|
490
|
+
`- **Reason:** ${reason}`,
|
|
491
|
+
"",
|
|
492
|
+
"This file was created automatically. To restart, delete this file and run `npx @xn-intenton-z2a/agentic-lib init --reseed`.",
|
|
493
|
+
].join("\n");
|
|
494
|
+
writeFileSync("MISSION_FAILED.md", signal);
|
|
495
|
+
core.info(`Mission failed signal written: ${reason}`);
|
|
496
|
+
if (process.env.GITHUB_REPOSITORY !== "xn-intenton-z2a/agentic-lib") {
|
|
497
|
+
try {
|
|
498
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
499
|
+
...repo,
|
|
500
|
+
workflow_id: "agentic-lib-schedule.yml",
|
|
501
|
+
ref: "main",
|
|
502
|
+
inputs: { frequency: "off" },
|
|
503
|
+
});
|
|
504
|
+
} catch (err) {
|
|
505
|
+
core.warning(`Could not set schedule to off: ${err.message}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return `mission-failed:${reason.substring(0, 100)}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
300
511
|
const ACTION_HANDLERS = {
|
|
301
512
|
"github:create-issue": executeCreateIssue,
|
|
302
513
|
"github:label-issue": executeLabelIssue,
|
|
303
514
|
"github:close-issue": executeCloseIssue,
|
|
304
515
|
"respond:discussions": executeRespondDiscussions,
|
|
516
|
+
"mission-complete": executeMissionComplete,
|
|
517
|
+
"mission-failed": executeMissionFailed,
|
|
305
518
|
};
|
|
306
519
|
|
|
307
520
|
async function executeSetSchedule(octokit, repo, frequency) {
|
|
@@ -309,6 +522,10 @@ async function executeSetSchedule(octokit, repo, frequency) {
|
|
|
309
522
|
if (!valid.includes(frequency)) {
|
|
310
523
|
return `skipped:invalid-frequency:${frequency}`;
|
|
311
524
|
}
|
|
525
|
+
if (process.env.GITHUB_REPOSITORY === "xn-intenton-z2a/agentic-lib") {
|
|
526
|
+
core.info(`Skipping schedule dispatch — running in SDK repo`);
|
|
527
|
+
return `skipped:sdk-repo:set-schedule:${frequency}`;
|
|
528
|
+
}
|
|
312
529
|
core.info(`Setting supervisor schedule to: ${frequency}`);
|
|
313
530
|
await octokit.rest.actions.createWorkflowDispatch({
|
|
314
531
|
...repo,
|
|
@@ -370,6 +587,17 @@ export async function supervise(context) {
|
|
|
370
587
|
}
|
|
371
588
|
}
|
|
372
589
|
|
|
590
|
+
// Build changes list from executed actions
|
|
591
|
+
const changes = results
|
|
592
|
+
.filter((r) => r.startsWith("created-issue:") || r.startsWith("mission-complete:") || r.startsWith("mission-failed:"))
|
|
593
|
+
.map((r) => {
|
|
594
|
+
if (r.startsWith("created-issue:")) return { action: "created-issue", file: r.replace("created-issue:", ""), sizeInfo: "" };
|
|
595
|
+
if (r.startsWith("mission-complete:")) return { action: "mission-complete", file: "MISSION_COMPLETE.md", sizeInfo: r.replace("mission-complete:", "") };
|
|
596
|
+
if (r.startsWith("mission-failed:")) return { action: "mission-failed", file: "MISSION_FAILED.md", sizeInfo: r.replace("mission-failed:", "") };
|
|
597
|
+
return null;
|
|
598
|
+
})
|
|
599
|
+
.filter(Boolean);
|
|
600
|
+
|
|
373
601
|
return {
|
|
374
602
|
outcome: actions.length === 0 ? "nop" : `supervised:${actions.length}-actions`,
|
|
375
603
|
tokensUsed,
|
|
@@ -378,5 +606,7 @@ export async function supervise(context) {
|
|
|
378
606
|
cost,
|
|
379
607
|
model,
|
|
380
608
|
details: `Actions: ${results.join(", ")}\nReasoning: ${reasoning.substring(0, 300)}`,
|
|
609
|
+
narrative: reasoning.substring(0, 500),
|
|
610
|
+
changes,
|
|
381
611
|
};
|
|
382
612
|
}
|
|
@@ -26,6 +26,8 @@ runs:
|
|
|
26
26
|
git config --local user.email 'action@github.com'
|
|
27
27
|
git config --local user.name 'GitHub Actions[bot]'
|
|
28
28
|
git add -A
|
|
29
|
+
# Unstage workflow files — GITHUB_TOKEN cannot push workflow changes
|
|
30
|
+
git reset HEAD -- '.github/workflows/' 2>/dev/null || true
|
|
29
31
|
# Unstage log files on non-default branches to avoid merge conflicts
|
|
30
32
|
REF="${{ inputs.push-ref }}"
|
|
31
33
|
if [ -n "$REF" ] && [ "$REF" != "main" ] && [ "$REF" != "master" ]; then
|
|
@@ -27,6 +27,8 @@ You are the supervisor of an autonomous coding repository. Your job is to advanc
|
|
|
27
27
|
- **github:close-issue** — When an issue is clearly resolved or no longer relevant.
|
|
28
28
|
- **respond:discussions** — When replying to a user request that came through the discussions bot. Include the discussion URL and a clear message.
|
|
29
29
|
- **set-schedule:\<frequency\>** — Change the workflow schedule. Use `weekly` when mission is substantially achieved, `continuous` to ramp up for active development.
|
|
30
|
+
- **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.
|
|
31
|
+
- **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.
|
|
30
32
|
- **nop** — When everything is running optimally: transform is active, issues are flowing, no failures.
|
|
31
33
|
|
|
32
34
|
## Stale Issue Detection
|
|
@@ -42,15 +44,16 @@ Include the website URL in the announcement — the site is at `https://<owner>.
|
|
|
42
44
|
|
|
43
45
|
### Mission Accomplished (bounded missions)
|
|
44
46
|
When ALL of the following conditions are met, the mission is accomplished:
|
|
45
|
-
1. All open issues are closed
|
|
47
|
+
1. All open issues are closed (check Recently Closed Issues — if the last 2+ were closed by review as RESOLVED, this is strong evidence)
|
|
46
48
|
2. Tests pass (CI gates commits, so this is usually the case)
|
|
47
|
-
3. The MISSION.md acceptance criteria are all satisfied
|
|
48
|
-
4.
|
|
49
|
+
3. The MISSION.md acceptance criteria are all satisfied (verify each criterion against the Recently Closed Issues and Recent Activity)
|
|
50
|
+
4. Do not create an issue if a similar issue was recently closed as resolved — check the Recently Closed Issues section
|
|
49
51
|
|
|
50
|
-
When all conditions are met:
|
|
51
|
-
1. `
|
|
52
|
-
2. `
|
|
53
|
-
|
|
52
|
+
When all conditions are met, use the `mission-complete` action:
|
|
53
|
+
1. `mission-complete | reason: <summary of what was achieved>` — this writes MISSION_COMPLETE.md and sets the schedule to off
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
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.
|
|
54
57
|
|
|
55
58
|
### Ongoing Missions
|
|
56
59
|
If MISSION.md explicitly says "do not set schedule to off" or "ongoing mission", the mission never completes.
|
|
@@ -64,14 +67,33 @@ When the transform agent has implemented all major features but minor polish rem
|
|
|
64
67
|
2. `set-schedule:weekly` — reduce to weekly maintenance check-ins
|
|
65
68
|
3. Check that `docs/` contains evidence of the library working before declaring done
|
|
66
69
|
|
|
70
|
+
### Mission Failed
|
|
71
|
+
When the mission cannot be completed, use the `mission-failed` action. Indicators of failure:
|
|
72
|
+
1. **Budget exhausted** — Transformation Budget shows usage at or near capacity with acceptance criteria still unmet
|
|
73
|
+
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)
|
|
74
|
+
3. **No progress** — the last 3+ transforms produced no code changes (all nop outcomes) and acceptance criteria remain unmet
|
|
75
|
+
4. **Repeated failures** — transforms keep producing code that fails tests, and fix-code cannot resolve the failures
|
|
76
|
+
5. **Consuming budget without results** — transformation budget is being spent but the codebase is not converging toward the acceptance criteria
|
|
77
|
+
|
|
78
|
+
When declaring mission failed:
|
|
79
|
+
1. `mission-failed | reason: <what went wrong and what was achieved>` — this writes MISSION_FAILED.md and sets the schedule to off
|
|
80
|
+
2. `dispatch:agentic-lib-bot` — announce the failure in the discussions thread with details of what was accomplished and what remains
|
|
81
|
+
|
|
67
82
|
## Prerequisites
|
|
68
83
|
|
|
69
84
|
- The `set-schedule` action requires a `WORKFLOW_TOKEN` secret (classic PAT with `workflow` scope) to push workflow file changes to main.
|
|
70
85
|
|
|
71
86
|
## Stability Detection
|
|
72
87
|
|
|
73
|
-
Check the Recent Activity log
|
|
74
|
-
|
|
88
|
+
Check the Recent Activity log and Recently Closed Issues for patterns:
|
|
89
|
+
|
|
90
|
+
**Mission complete signals:**
|
|
91
|
+
- 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`.
|
|
92
|
+
- 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.
|
|
93
|
+
|
|
94
|
+
**Mission failed signals:**
|
|
95
|
+
- 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`.
|
|
96
|
+
- 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`.
|
|
75
97
|
- Look for `transform: nop` or `transform: transformed` patterns in the activity log to distinguish productive iterations from idle ones.
|
|
76
98
|
|
|
77
99
|
## Discussions Awareness
|
|
@@ -14,9 +14,6 @@
|
|
|
14
14
|
.identity { background: #f6f8fa; padding: 1rem; border-radius: 6px; margin: 1rem 0; font-family: monospace; }
|
|
15
15
|
.identity dt { font-weight: bold; color: #666; }
|
|
16
16
|
.identity dd { margin: 0 0 0.5rem 0; font-size: 1.1rem; }
|
|
17
|
-
.social { display: flex; gap: 1rem; margin: 1rem 0; flex-wrap: wrap; }
|
|
18
|
-
.social a { display: inline-block; padding: 0.4rem 0.8rem; border: 1px solid #ddd; border-radius: 4px; text-decoration: none; font-size: 0.9rem; }
|
|
19
|
-
.social a:hover { background: #f6f8fa; }
|
|
20
17
|
#demo-output { background: #1e1e1e; color: #d4d4d4; padding: 1rem; border-radius: 6px; font-family: monospace; white-space: pre-wrap; min-height: 2rem; }
|
|
21
18
|
</style>
|
|
22
19
|
</head>
|
|
@@ -32,12 +29,6 @@
|
|
|
32
29
|
</div>
|
|
33
30
|
<h2>Output</h2>
|
|
34
31
|
<div id="demo-output">Loading library...</div>
|
|
35
|
-
<h2>Links</h2>
|
|
36
|
-
<div class="social">
|
|
37
|
-
<a id="share-github" href="https://github.com/xn-intenton-z2a/repository0" target="_blank" rel="noopener">GitHub</a>
|
|
38
|
-
<a id="share-twitter" href="#" target="_blank" rel="noopener">X/Twitter</a>
|
|
39
|
-
<a id="share-linkedin" href="#" target="_blank" rel="noopener">LinkedIn</a>
|
|
40
|
-
</div>
|
|
41
32
|
<h2>About</h2>
|
|
42
33
|
<p>This website uses the library. See <a href="https://github.com/xn-intenton-z2a/repository0">the repository</a> for source code and mission details.</p>
|
|
43
34
|
<script type="module">
|
|
@@ -48,10 +39,6 @@
|
|
|
48
39
|
document.getElementById('lib-description').textContent = description || '(no description)';
|
|
49
40
|
document.getElementById('demo-output').textContent = JSON.stringify({ name, version, description }, null, 2);
|
|
50
41
|
document.title = name + ' v' + version;
|
|
51
|
-
const pageUrl = encodeURIComponent(window.location.href);
|
|
52
|
-
const text = encodeURIComponent('Check out ' + name + ' v' + version + ' — ' + description);
|
|
53
|
-
document.getElementById('share-twitter').href = 'https://x.com/intent/tweet?url=' + pageUrl + '&text=' + text;
|
|
54
|
-
document.getElementById('share-linkedin').href = 'https://www.linkedin.com/sharing/share-offsite/?url=' + pageUrl;
|
|
55
42
|
} catch (e) {
|
|
56
43
|
document.getElementById('lib-version').textContent = '(build required)';
|
|
57
44
|
document.getElementById('lib-description').textContent = 'Run npm run build:web to generate library metadata';
|
|
@@ -25,9 +25,4 @@ describe("Website", () => {
|
|
|
25
25
|
expect(html).toContain("lib-name");
|
|
26
26
|
expect(html).toContain("lib-version");
|
|
27
27
|
});
|
|
28
|
-
|
|
29
|
-
test("index.html has social share links", () => {
|
|
30
|
-
const html = readFileSync("src/web/index.html", "utf8");
|
|
31
|
-
expect(html).toMatch(/share.*(twitter|x\.com|linkedin)/is);
|
|
32
|
-
});
|
|
33
28
|
});
|