@xn-intenton-z2a/agentic-lib 7.1.71 → 7.1.73

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.
@@ -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: Close GitHub Issues + Lock Discussions ---");
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
- // Close all open issues
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 issuesJson = execSync(`gh issue list --repo ${repoSlug} --state open --json number,title --limit 100`, {
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: 30000,
1022
- stdio: ["pipe", "pipe", "pipe"],
1023
- });
1024
- const issues = JSON.parse(issuesJson || "[]");
1025
- if (issues.length === 0) {
1026
- console.log(" No open issues to close");
1027
- } else {
1028
- for (const issue of issues) {
1029
- console.log(` CLOSE: issue #${issue.number} — ${issue.title}`);
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
- execSync(
1033
- `gh issue close ${issue.number} --repo ${repoSlug} --comment "Closed by init --purge (mission reset)"`,
1034
- { cwd: target, encoding: "utf8", timeout: 15000, stdio: "pipe" },
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 close issue #${issue.number}: ${err.message}`);
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: Could not list issues: ${err.message}`);
1276
+ console.log(` WARN: Label cleanup failed: ${err.message}`);
1047
1277
  }
1048
1278
 
1049
- // Close open discussions
1050
- const [owner, repo] = repoSlug.split("/");
1279
+ // ── Discussions: close all open, create fresh one ─────────────────
1280
+ console.log("\n --- Discussions: close open, create fresh ---");
1051
1281
  try {
1052
- const query = JSON.stringify({
1053
- query: `{ repository(owner:"${owner}", name:"${repo}") { discussions(first:50, states:OPEN) { nodes { id number title } } } }`,
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
- const mutation = JSON.stringify({
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
- // Get the repository node ID and "General" category ID
1097
- const repoQuery = JSON.stringify({
1098
- query: `{ repository(owner:"${owner}", name:"${repo}") { id discussionCategories(first:20) { nodes { id name } } } }`,
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 createMutation = JSON.stringify({
1117
- query: `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 } } }`,
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 (serve from docs/ on main branch)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xn-intenton-z2a/agentic-lib",
3
- "version": "7.1.71",
3
+ "version": "7.1.73",
4
4
  "description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -19,6 +19,7 @@
19
19
  "test:copilot": "node scripts/test-copilot-local.js",
20
20
  "test:discussions": "node scripts/test-discussions-local.js",
21
21
  "test:transform": "node scripts/test-transform-local.js ../repository0",
22
+ "test:spike-llm": "vitest --run tests/spike-local-llm.test.js",
22
23
  "test:system": "bash scripts/system-test.sh --init-only",
23
24
  "test:system:dry-run": "bash scripts/system-test.sh --dry-run",
24
25
  "test:system:live": "bash scripts/system-test.sh",
@@ -40,6 +41,7 @@
40
41
  "js-yaml": "^4.1.1",
41
42
  "markdown-it": "^14.1.1",
42
43
  "markdown-it-github": "^0.5.0",
44
+ "node-llama-cpp": "^3.17.1",
43
45
  "prettier": "^3.8.1",
44
46
  "vitest": "^4.0.18"
45
47
  },
@@ -271,6 +271,7 @@ export function loadConfig(configPath) {
271
271
  intentionBot: {
272
272
  intentionFilepath: bot["log-file"] || "intentïon.md",
273
273
  },
274
+ init: toml.init || null,
274
275
  tdd: toml.tdd === true,
275
276
  writablePaths,
276
277
  readOnlyPaths,
@@ -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 = context.config?.tuning || {};
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:
@@ -205,33 +284,44 @@ export async function discussions(context) {
205
284
  }
206
285
  }
207
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
+
208
290
  // Request supervisor evaluation
209
291
  if (action === "request-supervisor") {
210
- try {
211
- await octokit.rest.actions.createWorkflowDispatch({
212
- ...context.repo,
213
- workflow_id: "agentic-lib-workflow.yml",
214
- ref: "main",
215
- inputs: { message: actionArg || "Discussion bot referral" },
216
- });
217
- core.info(`Dispatched supervisor with message: ${actionArg}`);
218
- } catch (err) {
219
- core.warning(`Failed to dispatch supervisor: ${err.message}`);
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
+ }
220
306
  }
221
307
  }
222
308
 
223
309
  // Stop automation
224
310
  if (action === "stop") {
225
- try {
226
- await octokit.rest.actions.createWorkflowDispatch({
227
- ...context.repo,
228
- workflow_id: "agentic-lib-schedule.yml",
229
- ref: "main",
230
- inputs: { frequency: "off" },
231
- });
232
- core.info("Automation stopped via discussions bot");
233
- } catch (err) {
234
- core.warning(`Failed to stop automation: ${err.message}`);
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
+ }
235
325
  }
236
326
  }
237
327
 
@@ -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
  "",
@@ -110,13 +110,55 @@ async function gatherContext(octokit, repo, config, t) {
110
110
  return `#${pr.number}: ${pr.title} (${pr.head.ref}) [${labels || "no labels"}] (${age}d old)`;
111
111
  });
112
112
 
113
+ // Read init timestamp for epoch boundary
114
+ const initTimestamp = config.init?.timestamp || null;
115
+
113
116
  let workflowsSummary = [];
117
+ let actionsSinceInit = [];
114
118
  try {
115
119
  const { data: runs } = await octokit.rest.actions.listWorkflowRunsForRepo({
116
120
  ...repo,
117
- per_page: 10,
121
+ per_page: 20,
118
122
  });
119
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
+ }
120
162
  } catch (err) {
121
163
  core.warning(`Could not fetch workflow runs: ${err.message}`);
122
164
  }
@@ -132,6 +174,8 @@ async function gatherContext(octokit, repo, config, t) {
132
174
  oldestReadyIssue,
133
175
  prsSummary,
134
176
  workflowsSummary,
177
+ actionsSinceInit,
178
+ initTimestamp,
135
179
  supervisor: config.supervisor,
136
180
  configToml: config.configToml,
137
181
  packageJson: config.packageJson,
@@ -175,6 +219,20 @@ function buildPrompt(ctx, agentInstructions) {
175
219
  `### Recent Workflow Runs`,
176
220
  ctx.workflowsSummary.join("\n") || "none",
177
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
+ : []),
178
236
  `### Recent Activity`,
179
237
  ctx.recentActivity || "none",
180
238
  "",
@@ -289,6 +347,12 @@ async function executeDispatch(octokit, repo, actionName, params) {
289
347
  if (params["pr-number"]) inputs["pr-number"] = params["pr-number"];
290
348
  if (params["issue-number"]) inputs["issue-number"] = params["issue-number"];
291
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
+
292
356
  // Guard: skip transform dispatch if one is already running
293
357
  if (workflowFile === "agentic-lib-workflow.yml") {
294
358
  try {
@@ -370,6 +434,10 @@ async function executeRespondDiscussions(octokit, repo, params) {
370
434
  const message = params.message || "";
371
435
  const url = params["discussion-url"] || "";
372
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
+ }
373
441
  core.info(`Dispatching discussions bot with response: ${message.substring(0, 100)}`);
374
442
  const inputs = { message };
375
443
  if (url) inputs["discussion-url"] = url;
@@ -397,15 +465,17 @@ async function executeMissionComplete(octokit, repo, params) {
397
465
  ].join("\n");
398
466
  writeFileSync("MISSION_COMPLETE.md", signal);
399
467
  core.info(`Mission complete signal written: ${reason}`);
400
- try {
401
- await octokit.rest.actions.createWorkflowDispatch({
402
- ...repo,
403
- workflow_id: "agentic-lib-schedule.yml",
404
- ref: "main",
405
- inputs: { frequency: "off" },
406
- });
407
- } catch (err) {
408
- core.warning(`Could not set schedule to off: ${err.message}`);
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
+ }
409
479
  }
410
480
  return `mission-complete:${reason.substring(0, 100)}`;
411
481
  }
@@ -423,15 +493,17 @@ async function executeMissionFailed(octokit, repo, params) {
423
493
  ].join("\n");
424
494
  writeFileSync("MISSION_FAILED.md", signal);
425
495
  core.info(`Mission failed signal written: ${reason}`);
426
- try {
427
- await octokit.rest.actions.createWorkflowDispatch({
428
- ...repo,
429
- workflow_id: "agentic-lib-schedule.yml",
430
- ref: "main",
431
- inputs: { frequency: "off" },
432
- });
433
- } catch (err) {
434
- core.warning(`Could not set schedule to off: ${err.message}`);
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
+ }
435
507
  }
436
508
  return `mission-failed:${reason.substring(0, 100)}`;
437
509
  }
@@ -450,6 +522,10 @@ async function executeSetSchedule(octokit, repo, frequency) {
450
522
  if (!valid.includes(frequency)) {
451
523
  return `skipped:invalid-frequency:${frequency}`;
452
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
+ }
453
529
  core.info(`Setting supervisor schedule to: ${frequency}`);
454
530
  await octokit.rest.actions.createWorkflowDispatch({
455
531
  ...repo,
@@ -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
@@ -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';
@@ -16,7 +16,7 @@
16
16
  "author": "",
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
- "@xn-intenton-z2a/agentic-lib": "^7.1.71"
19
+ "@xn-intenton-z2a/agentic-lib": "^7.1.73"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@vitest/coverage-v8": "^4.0.18",
@@ -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
  });