@xn-intenton-z2a/agentic-lib 7.1.23 → 7.1.25

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.
@@ -738,43 +738,38 @@ function initConfig(seedsDir) {
738
738
  }
739
739
  }
740
740
 
741
- function initReseed() {
742
- console.log("\n--- Reseed: Clear Features + Activity Log ---");
743
- const intentionFile = resolve(target, "intentïon.md");
744
- if (existsSync(intentionFile)) {
745
- if (!dryRun) rmSync(intentionFile);
746
- console.log(" REMOVE: intentïon.md");
741
+ function removeFile(filePath, label) {
742
+ if (existsSync(filePath)) {
743
+ if (!dryRun) rmSync(filePath);
744
+ console.log(` REMOVE: ${label}`);
747
745
  initChanges++;
748
746
  }
749
- // Clear features directory (now at project root, next to library/)
750
- const featuresDir = resolve(target, "features");
751
- if (existsSync(featuresDir)) {
752
- for (const f of readdirSync(featuresDir)) {
753
- if (!dryRun) rmSync(resolve(featuresDir, f));
754
- console.log(` REMOVE: features/${f}`);
755
- initChanges++;
756
- }
747
+ }
748
+
749
+ function clearDirContents(dirPath, label) {
750
+ if (!existsSync(dirPath)) return;
751
+ for (const f of readdirSync(dirPath)) {
752
+ if (!dryRun) rmSync(resolve(dirPath, f));
753
+ console.log(` REMOVE: ${label}/${f}`);
754
+ initChanges++;
757
755
  }
758
- // Also clear old features location if it exists
756
+ }
757
+
758
+ function initReseed() {
759
+ console.log("\n--- Reseed: Clear Features + Activity Log ---");
760
+ removeFile(resolve(target, "intentïon.md"), "intentïon.md");
761
+ clearDirContents(resolve(target, "features"), "features");
762
+
763
+ // Clear old features location if it exists
759
764
  const oldFeaturesDir = resolve(target, ".github/agentic-lib/features");
765
+ clearDirContents(oldFeaturesDir, ".github/agentic-lib/features (old location)");
760
766
  if (existsSync(oldFeaturesDir)) {
761
- for (const f of readdirSync(oldFeaturesDir)) {
762
- if (!dryRun) rmSync(resolve(oldFeaturesDir, f));
763
- console.log(` REMOVE: .github/agentic-lib/features/${f} (old location)`);
764
- initChanges++;
765
- }
766
767
  if (!dryRun) rmdirSync(oldFeaturesDir);
767
768
  console.log(" REMOVE: .github/agentic-lib/features/ (old location)");
768
769
  }
769
- // Clear library directory (generated from SOURCES.md)
770
- const libraryDir = resolve(target, "library");
771
- if (existsSync(libraryDir)) {
772
- for (const f of readdirSync(libraryDir)) {
773
- if (!dryRun) rmSync(resolve(libraryDir, f));
774
- console.log(` REMOVE: library/${f}`);
775
- initChanges++;
776
- }
777
- }
770
+
771
+ clearDirContents(resolve(target, "library"), "library");
772
+
778
773
  // Remove old getting-started-guide if it exists
779
774
  const oldGuideDir = resolve(target, ".github/agentic-lib/getting-started-guide");
780
775
  if (existsSync(oldGuideDir)) {
@@ -784,10 +779,7 @@ function initReseed() {
784
779
  }
785
780
  }
786
781
 
787
- function initPurge(seedsDir) {
788
- console.log("\n--- Purge: Reset Source Files to Seed State ---");
789
-
790
- // Read TOML to get source and tests paths (or use defaults)
782
+ function readTomlPaths() {
791
783
  let sourcePath = "src/lib/";
792
784
  let testsPath = "tests/unit/";
793
785
  const tomlTarget = resolve(target, "agentic-lib.toml");
@@ -802,24 +794,25 @@ function initPurge(seedsDir) {
802
794
  console.log(` WARN: Could not read TOML for paths, using defaults: ${err.message}`);
803
795
  }
804
796
  }
797
+ return { sourcePath, testsPath };
798
+ }
805
799
 
806
- // Clear source directory completely
807
- const sourceDir = resolve(target, sourcePath);
808
- if (existsSync(sourceDir)) {
809
- console.log(` CLEAR: ${sourcePath}`);
810
- if (!dryRun) rmSync(sourceDir, { recursive: true });
800
+ function clearAndRecreateDir(dirPath, label) {
801
+ const fullPath = resolve(target, dirPath);
802
+ if (existsSync(fullPath)) {
803
+ console.log(` CLEAR: ${label}`);
804
+ if (!dryRun) rmSync(fullPath, { recursive: true });
811
805
  initChanges++;
812
806
  }
813
- if (!dryRun) mkdirSync(sourceDir, { recursive: true });
807
+ if (!dryRun) mkdirSync(fullPath, { recursive: true });
808
+ }
814
809
 
815
- // Clear tests directory completely
816
- const testsDir = resolve(target, testsPath);
817
- if (existsSync(testsDir)) {
818
- console.log(` CLEAR: ${testsPath}`);
819
- if (!dryRun) rmSync(testsDir, { recursive: true });
820
- initChanges++;
821
- }
822
- if (!dryRun) mkdirSync(testsDir, { recursive: true });
810
+ function initPurge(seedsDir) {
811
+ console.log("\n--- Purge: Reset Source Files to Seed State ---");
812
+
813
+ const { sourcePath, testsPath } = readTomlPaths();
814
+ clearAndRecreateDir(sourcePath, sourcePath);
815
+ clearAndRecreateDir(testsPath, testsPath);
823
816
 
824
817
  // Copy seed files
825
818
  const SEED_MAP = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xn-intenton-z2a/agentic-lib",
3
- "version": "7.1.23",
3
+ "version": "7.1.25",
4
4
  "description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -11,127 +11,95 @@ import { runCopilotTask, readOptionalFile, scanDirectory } from "../copilot.js";
11
11
 
12
12
  const BOT_LOGINS = ["github-actions[bot]", "github-actions"];
13
13
 
14
- /**
15
- * Respond to a GitHub Discussion using the Copilot SDK.
16
- *
17
- * @param {Object} context - Task context from index.js
18
- * @returns {Promise<Object>} Result with outcome, action, tokensUsed, model
19
- */
20
- export async function discussions(context) {
21
- const { octokit, config, instructions, model, discussionUrl } = context;
22
-
23
- if (!discussionUrl) {
24
- throw new Error("discussions task requires discussion-url input");
14
+ async function fetchDiscussion(octokit, discussionUrl) {
15
+ const urlMatch = discussionUrl.match(/github\.com\/([^/]+)\/([^/]+)\/discussions\/(\d+)/);
16
+ if (!urlMatch) {
17
+ core.warning(`Could not parse discussion URL: ${discussionUrl}`);
18
+ return { title: "", body: "", comments: [], nodeId: "" };
25
19
  }
26
20
 
27
- // Parse discussion URL and fetch content via GraphQL
28
- const urlMatch = discussionUrl.match(/github\.com\/([^/]+)\/([^/]+)\/discussions\/(\d+)/);
29
- let discussionTitle = "";
30
- let discussionBody = "";
31
- let discussionComments = [];
32
- let discussionNodeId = "";
33
-
34
- if (urlMatch) {
35
- const [, urlOwner, urlRepo, discussionNumber] = urlMatch;
36
- try {
37
- const query = `query {
38
- repository(owner: "${urlOwner}", name: "${urlRepo}") {
39
- discussion(number: ${discussionNumber}) {
40
- id
41
- title
42
- body
43
- comments(last: 10) {
44
- nodes {
45
- body
46
- author { login }
47
- createdAt
48
- }
21
+ const [, urlOwner, urlRepo, discussionNumber] = urlMatch;
22
+ try {
23
+ const query = `query {
24
+ repository(owner: "${urlOwner}", name: "${urlRepo}") {
25
+ discussion(number: ${discussionNumber}) {
26
+ id
27
+ title
28
+ body
29
+ comments(last: 10) {
30
+ nodes {
31
+ body
32
+ author { login }
33
+ createdAt
49
34
  }
50
35
  }
51
36
  }
52
- }`;
53
- const result = await octokit.graphql(query);
54
- const discussion = result.repository.discussion;
55
- discussionNodeId = discussion.id || "";
56
- discussionTitle = discussion.title || "";
57
- discussionBody = discussion.body || "";
58
- discussionComments = discussion.comments.nodes || [];
59
- core.info(
60
- `Fetched discussion #${discussionNumber}: "${discussionTitle}" (${discussionComments.length} comments)`,
61
- );
62
- } catch (err) {
63
- core.warning(`Failed to fetch discussion content via GraphQL: ${err.message}. Falling back to URL-only.`);
64
- }
65
- } else {
66
- core.warning(`Could not parse discussion URL: ${discussionUrl}`);
37
+ }
38
+ }`;
39
+ const result = await octokit.graphql(query);
40
+ const discussion = result.repository.discussion;
41
+ core.info(
42
+ `Fetched discussion #${discussionNumber}: "${discussion.title}" (${discussion.comments.nodes.length} comments)`,
43
+ );
44
+ return {
45
+ title: discussion.title || "",
46
+ body: discussion.body || "",
47
+ comments: discussion.comments.nodes || [],
48
+ nodeId: discussion.id || "",
49
+ };
50
+ } catch (err) {
51
+ core.warning(`Failed to fetch discussion content via GraphQL: ${err.message}. Falling back to URL-only.`);
52
+ return { title: "", body: "", comments: [], nodeId: "" };
67
53
  }
54
+ }
68
55
 
69
- // Separate human comments from bot's own replies
70
- const humanComments = discussionComments.filter(
71
- (c) => !BOT_LOGINS.includes(c.author?.login),
72
- );
73
- const botReplies = discussionComments.filter(
74
- (c) => BOT_LOGINS.includes(c.author?.login),
75
- );
56
+ function buildPrompt(discussionUrl, discussion, context) {
57
+ const { config, instructions } = context;
58
+ const { title, body, comments } = discussion;
59
+
60
+ const humanComments = comments.filter((c) => !BOT_LOGINS.includes(c.author?.login));
61
+ const botReplies = comments.filter((c) => BOT_LOGINS.includes(c.author?.login));
76
62
  const latestHumanComment = humanComments.length > 0 ? humanComments[humanComments.length - 1] : null;
77
63
  const lastBotReply = botReplies.length > 0 ? botReplies[botReplies.length - 1] : null;
78
64
 
79
65
  const mission = readOptionalFile(config.paths.mission.path);
80
66
  const contributing = readOptionalFile(config.paths.contributing.path, 1000);
81
-
82
67
  const featuresPath = config.paths.features.path;
83
- let featureNames = [];
84
- if (existsSync(featuresPath)) {
85
- featureNames = scanDirectory(featuresPath, ".md").map((f) => f.name.replace(".md", ""));
86
- }
87
-
88
- const intentionPath = config.intentionBot.intentionFilepath;
89
- const recentActivity = readOptionalFile(intentionPath).split("\n").slice(-20).join("\n");
90
-
68
+ const featureNames = existsSync(featuresPath)
69
+ ? scanDirectory(featuresPath, ".md").map((f) => f.name.replace(".md", ""))
70
+ : [];
71
+ const recentActivity = readOptionalFile(config.intentionBot.intentionFilepath).split("\n").slice(-20).join("\n");
91
72
  const agentInstructions = instructions || "Respond to the GitHub Discussion as the repository bot.";
92
73
 
93
- // Build thread-aware prompt
94
- const promptParts = [
74
+ const parts = [
95
75
  "## Instructions",
96
76
  agentInstructions,
97
77
  "",
98
78
  "## Discussion Thread",
99
79
  `URL: ${discussionUrl}`,
100
- discussionTitle ? `### ${discussionTitle}` : "",
101
- discussionBody || "(no body)",
80
+ title ? `### ${title}` : "",
81
+ body || "(no body)",
102
82
  ];
103
83
 
104
- // Show conversation history (human comments only, with timestamps)
105
84
  if (humanComments.length > 0) {
106
- promptParts.push("", "### Conversation History");
85
+ parts.push("", "### Conversation History");
107
86
  for (const c of humanComments) {
108
- const isLatest = c === latestHumanComment;
109
- const prefix = isLatest ? ">>> **[LATEST — RESPOND TO THIS]** " : "";
110
- promptParts.push(`${prefix}**${c.author?.login || "unknown"}** (${c.createdAt}):\n${c.body}`);
87
+ const prefix = c === latestHumanComment ? ">>> **[LATEST — RESPOND TO THIS]** " : "";
88
+ parts.push(`${prefix}**${c.author?.login || "unknown"}** (${c.createdAt}):\n${c.body}`);
111
89
  }
112
90
  }
113
91
 
114
- // Show the bot's last reply so it knows what it already said
115
92
  if (lastBotReply) {
116
- promptParts.push(
117
- "",
118
- "### Your Last Reply (DO NOT REPEAT THIS)",
119
- lastBotReply.body.substring(0, 500),
120
- );
93
+ parts.push("", "### Your Last Reply (DO NOT REPEAT THIS)", lastBotReply.body.substring(0, 500));
121
94
  }
122
95
 
123
- // Context section
124
- promptParts.push(
96
+ parts.push(
125
97
  "",
126
98
  "## Repository Context",
127
99
  `### Mission\n${mission}`,
128
100
  contributing ? `### Contributing\n${contributing}` : "",
129
101
  `### Current Features\n${featureNames.join(", ") || "none"}`,
130
102
  recentActivity ? `### Recent Activity\n${recentActivity}` : "",
131
- );
132
-
133
- // Actions — concise, not a wall of text
134
- promptParts.push(
135
103
  "",
136
104
  "## Actions",
137
105
  "Include exactly one action tag in your response. Only mention actions to the user when relevant.",
@@ -146,7 +114,49 @@ export async function discussions(context) {
146
114
  "`[ACTION:stop]` — Halt automation",
147
115
  );
148
116
 
149
- const prompt = promptParts.filter(Boolean).join("\n");
117
+ return parts.filter(Boolean).join("\n");
118
+ }
119
+
120
+ async function postReply(octokit, nodeId, replyBody) {
121
+ if (!nodeId) {
122
+ core.warning("Cannot post reply: discussion node ID not available");
123
+ return;
124
+ }
125
+ if (!replyBody) {
126
+ core.warning("Cannot post reply: no reply content generated");
127
+ return;
128
+ }
129
+ try {
130
+ const mutation = `mutation($discussionId: ID!, $body: String!) {
131
+ addDiscussionComment(input: { discussionId: $discussionId, body: $body }) {
132
+ comment { url }
133
+ }
134
+ }`;
135
+ const { addDiscussionComment } = await octokit.graphql(mutation, {
136
+ discussionId: nodeId,
137
+ body: replyBody,
138
+ });
139
+ core.info(`Posted reply to discussion: ${addDiscussionComment.comment.url}`);
140
+ } catch (err) {
141
+ core.warning(`Failed to post discussion reply: ${err.message}`);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Respond to a GitHub Discussion using the Copilot SDK.
147
+ *
148
+ * @param {Object} context - Task context from index.js
149
+ * @returns {Promise<Object>} Result with outcome, action, tokensUsed, model
150
+ */
151
+ export async function discussions(context) {
152
+ const { octokit, model, discussionUrl } = context;
153
+
154
+ if (!discussionUrl) {
155
+ throw new Error("discussions task requires discussion-url input");
156
+ }
157
+
158
+ const discussion = await fetchDiscussion(octokit, discussionUrl);
159
+ const prompt = buildPrompt(discussionUrl, discussion, context);
150
160
 
151
161
  const { content, tokensUsed } = await runCopilotTask({
152
162
  model,
@@ -156,35 +166,13 @@ export async function discussions(context) {
156
166
  writablePaths: [],
157
167
  });
158
168
 
159
- // Parse action from response
160
169
  const actionMatch = content.match(/\[ACTION:(\S+?)\](.+)?/);
161
170
  const action = actionMatch ? actionMatch[1] : "nop";
162
171
  const actionArg = actionMatch && actionMatch[2] ? actionMatch[2].trim() : "";
163
172
  const replyBody = content.replace(/\[ACTION:\S+?\].+/, "").trim();
164
173
 
165
174
  core.info(`Discussion bot action: ${action}, arg: ${actionArg}`);
166
-
167
- // Post reply comment back to the Discussion
168
- if (discussionNodeId && replyBody) {
169
- try {
170
- const mutation = `mutation($discussionId: ID!, $body: String!) {
171
- addDiscussionComment(input: { discussionId: $discussionId, body: $body }) {
172
- comment { url }
173
- }
174
- }`;
175
- const { addDiscussionComment } = await octokit.graphql(mutation, {
176
- discussionId: discussionNodeId,
177
- body: replyBody,
178
- });
179
- core.info(`Posted reply to discussion: ${addDiscussionComment.comment.url}`);
180
- } catch (err) {
181
- core.warning(`Failed to post discussion reply: ${err.message}`);
182
- }
183
- } else if (!discussionNodeId) {
184
- core.warning("Cannot post reply: discussion node ID not available");
185
- } else if (!replyBody) {
186
- core.warning("Cannot post reply: no reply content generated");
187
- }
175
+ await postReply(octokit, discussion.nodeId, replyBody);
188
176
 
189
177
  const argSuffix = actionArg ? ` (${actionArg})` : "";
190
178
  return {
@@ -14,7 +14,7 @@
14
14
  "author": "",
15
15
  "license": "MIT",
16
16
  "dependencies": {
17
- "@xn-intenton-z2a/agentic-lib": "^7.1.23"
17
+ "@xn-intenton-z2a/agentic-lib": "^7.1.25"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@vitest/coverage-v8": "^4.0.0",
@@ -33,6 +33,7 @@ jobs:
33
33
  - uses: actions/checkout@v4
34
34
  with:
35
35
  ref: main
36
+ token: ${{ secrets.WORKFLOW_TOKEN }}
36
37
 
37
38
  - name: Update supervisor schedule
38
39
  uses: actions/github-script@v7