@xn-intenton-z2a/agentic-lib 7.1.23 → 7.1.24
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/bin/agentic-lib.js
CHANGED
|
@@ -738,43 +738,38 @@ function initConfig(seedsDir) {
|
|
|
738
738
|
}
|
|
739
739
|
}
|
|
740
740
|
|
|
741
|
-
function
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
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
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
|
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
|
-
|
|
807
|
-
const
|
|
808
|
-
if (existsSync(
|
|
809
|
-
console.log(` CLEAR: ${
|
|
810
|
-
if (!dryRun) rmSync(
|
|
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(
|
|
807
|
+
if (!dryRun) mkdirSync(fullPath, { recursive: true });
|
|
808
|
+
}
|
|
814
809
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
core.warning(`
|
|
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
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
94
|
-
const promptParts = [
|
|
74
|
+
const parts = [
|
|
95
75
|
"## Instructions",
|
|
96
76
|
agentInstructions,
|
|
97
77
|
"",
|
|
98
78
|
"## Discussion Thread",
|
|
99
79
|
`URL: ${discussionUrl}`,
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
85
|
+
parts.push("", "### Conversation History");
|
|
107
86
|
for (const c of humanComments) {
|
|
108
|
-
const
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|