@xn-intenton-z2a/agentic-lib 7.1.6

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.
Files changed (53) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +323 -0
  3. package/bin/agentic-lib.js +765 -0
  4. package/package.json +102 -0
  5. package/src/actions/agentic-step/action.yml +58 -0
  6. package/src/actions/agentic-step/config-loader.js +153 -0
  7. package/src/actions/agentic-step/copilot.js +170 -0
  8. package/src/actions/agentic-step/index.js +118 -0
  9. package/src/actions/agentic-step/logging.js +88 -0
  10. package/src/actions/agentic-step/package-lock.json +1891 -0
  11. package/src/actions/agentic-step/package.json +29 -0
  12. package/src/actions/agentic-step/safety.js +103 -0
  13. package/src/actions/agentic-step/tasks/discussions.js +141 -0
  14. package/src/actions/agentic-step/tasks/enhance-issue.js +102 -0
  15. package/src/actions/agentic-step/tasks/fix-code.js +71 -0
  16. package/src/actions/agentic-step/tasks/maintain-features.js +79 -0
  17. package/src/actions/agentic-step/tasks/maintain-library.js +67 -0
  18. package/src/actions/agentic-step/tasks/resolve-issue.js +98 -0
  19. package/src/actions/agentic-step/tasks/review-issue.js +121 -0
  20. package/src/actions/agentic-step/tasks/transform.js +213 -0
  21. package/src/actions/agentic-step/tools.js +142 -0
  22. package/src/actions/commit-if-changed/action.yml +39 -0
  23. package/src/actions/setup-npmrc/action.yml +38 -0
  24. package/src/agents/agent-apply-fix.md +13 -0
  25. package/src/agents/agent-discussion-bot.md +35 -0
  26. package/src/agents/agent-issue-resolution.md +13 -0
  27. package/src/agents/agent-maintain-features.md +29 -0
  28. package/src/agents/agent-maintain-library.md +31 -0
  29. package/src/agents/agent-ready-issue.md +13 -0
  30. package/src/agents/agent-review-issue.md +2 -0
  31. package/src/agents/agentic-lib.yml +68 -0
  32. package/src/scripts/accept-release.sh +29 -0
  33. package/src/scripts/activate-schedule.sh +41 -0
  34. package/src/scripts/clean.sh +21 -0
  35. package/src/scripts/generate-library-index.js +143 -0
  36. package/src/scripts/initialise.sh +39 -0
  37. package/src/scripts/md-to-html.js +77 -0
  38. package/src/scripts/update.sh +19 -0
  39. package/src/seeds/test.yml +33 -0
  40. package/src/seeds/zero-MISSION.md +7 -0
  41. package/src/seeds/zero-README.md +14 -0
  42. package/src/seeds/zero-agentic-lib.toml +32 -0
  43. package/src/seeds/zero-main.js +15 -0
  44. package/src/seeds/zero-main.test.js +11 -0
  45. package/src/seeds/zero-package.json +26 -0
  46. package/src/workflows/agent-discussions-bot.yml +78 -0
  47. package/src/workflows/agent-flow-fix-code.yml +98 -0
  48. package/src/workflows/agent-flow-maintain.yml +114 -0
  49. package/src/workflows/agent-flow-review.yml +99 -0
  50. package/src/workflows/agent-flow-transform.yml +82 -0
  51. package/src/workflows/agent-supervisor.yml +85 -0
  52. package/src/workflows/ci-automerge.yml +544 -0
  53. package/src/workflows/ci-init.yml +63 -0
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "@xn-intenton-z2a/agentic-lib",
3
+ "version": "7.1.6",
4
+ "description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "echo \"Nothing to build\"",
8
+ "formatting": "prettier --check '**/*.{js,json,yml,md}'",
9
+ "formatting-fix": "prettier --write '**/*.{js,json,yml,md}'",
10
+ "linting": "eslint",
11
+ "linting-json": "eslint --format=@microsoft/eslint-formatter-sarif",
12
+ "linting-fix": "eslint --fix",
13
+ "update-to-minor": "npx npm-check-updates --upgrade --enginesNode --target minor --verbose --install always",
14
+ "update-to-greatest": "npx npm-check-updates --upgrade --enginesNode --target greatest --verbose --install always --reject \"alpha\"",
15
+ "lint:workflows": "node scripts/validate-workflows.js",
16
+ "security": "npm audit --audit-level=high",
17
+ "test": "vitest --run",
18
+ "test:smoke": "node scripts/smoke-test-connectivity.js",
19
+ "test:copilot": "node scripts/test-copilot-local.js",
20
+ "test:discussions": "node scripts/test-discussions-local.js",
21
+ "test:transform": "node scripts/test-transform-local.js ../repository0",
22
+ "test:system": "bash scripts/system-test.sh --init-only",
23
+ "test:system:dry-run": "bash scripts/system-test.sh --dry-run",
24
+ "test:system:live": "bash scripts/system-test.sh",
25
+ "start": "echo \"agentic-lib is a workflow SDK — see README.md\""
26
+ },
27
+ "keywords": [],
28
+ "author": "https://github.com/xn-intenton-z2a",
29
+ "license": "GPL-3.0, MIT",
30
+ "devDependencies": {
31
+ "@microsoft/eslint-formatter-sarif": "^3.1.0",
32
+ "eslint": "^9.25.0",
33
+ "eslint-config-google": "^0.14.0",
34
+ "eslint-config-prettier": "^10.1.8",
35
+ "eslint-plugin-import": "^2.31.0",
36
+ "eslint-plugin-prettier": "^5.4.0",
37
+ "eslint-plugin-promise": "^7.2.1",
38
+ "eslint-plugin-react": "^7.37.5",
39
+ "eslint-plugin-security": "^4.0.0",
40
+ "eslint-plugin-sonarjs": "^4.0.0",
41
+ "js-yaml": "^4.1.0",
42
+ "markdown-it": "^14.1.0",
43
+ "markdown-it-github": "^0.5.0",
44
+ "npm-check-updates": "^19.6.3",
45
+ "prettier": "^3.8.1",
46
+ "vitest": "^4.0.18"
47
+ },
48
+ "engines": {
49
+ "node": ">=24.0.0"
50
+ },
51
+ "bin": {
52
+ "agentic-lib": "bin/agentic-lib.js"
53
+ },
54
+ "exports": {
55
+ ".": "./bin/agentic-lib.js",
56
+ "./config": "./src/actions/agentic-step/config-loader.js",
57
+ "./copilot": "./src/actions/agentic-step/copilot.js",
58
+ "./safety": "./src/actions/agentic-step/safety.js",
59
+ "./logging": "./src/actions/agentic-step/logging.js",
60
+ "./tools": "./src/actions/agentic-step/tools.js",
61
+ "./tasks/transform": "./src/actions/agentic-step/tasks/transform.js",
62
+ "./tasks/maintain-features": "./src/actions/agentic-step/tasks/maintain-features.js",
63
+ "./tasks/maintain-library": "./src/actions/agentic-step/tasks/maintain-library.js",
64
+ "./tasks/fix-code": "./src/actions/agentic-step/tasks/fix-code.js",
65
+ "./tasks/resolve-issue": "./src/actions/agentic-step/tasks/resolve-issue.js",
66
+ "./tasks/discussions": "./src/actions/agentic-step/tasks/discussions.js",
67
+ "./tasks/enhance-issue": "./src/actions/agentic-step/tasks/enhance-issue.js",
68
+ "./tasks/review-issue": "./src/actions/agentic-step/tasks/review-issue.js"
69
+ },
70
+ "files": [
71
+ "package.json",
72
+ "bin/",
73
+ "src/workflows/",
74
+ "src/actions/agentic-step/*.js",
75
+ "src/actions/agentic-step/*.yml",
76
+ "src/actions/agentic-step/*.json",
77
+ "src/actions/agentic-step/tasks/",
78
+ "src/actions/commit-if-changed/",
79
+ "src/actions/setup-npmrc/",
80
+ "src/agents/",
81
+ "src/seeds/",
82
+ "src/scripts/"
83
+ ],
84
+ "overrides": {
85
+ "minimatch": ">=10.2.3"
86
+ },
87
+ "repository": {
88
+ "type": "git",
89
+ "url": "git+https://github.com/xn-intenton-z2a/agentic-lib.git"
90
+ },
91
+ "publishConfig": {
92
+ "registry": "https://registry.npmjs.org",
93
+ "access": "public",
94
+ "provenance": true
95
+ },
96
+ "dependencies": {
97
+ "@actions/core": "^3.0.0",
98
+ "@actions/github": "^9.0.0",
99
+ "@github/copilot-sdk": "^0.1.29",
100
+ "smol-toml": "^1.6.0"
101
+ }
102
+ }
@@ -0,0 +1,58 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2025-2026 Polycode Limited
3
+ # .github/agentic-lib/actions/agentic-step/action.yml
4
+ #
5
+ # agentic-step — A GitHub Action wrapping the GitHub Copilot SDK for autonomous code transformation.
6
+ # Part of the intentïon project: https://github.com/xn-intenton-z2a/agentic-lib
7
+
8
+ name: "agentic-step"
9
+ description: "Run an autonomous agentic task using the GitHub Copilot SDK"
10
+ author: "xn-intenton-z2a"
11
+
12
+ inputs:
13
+ task:
14
+ description: >
15
+ The task to perform. One of: resolve-issue, fix-code, transform, maintain-features,
16
+ maintain-library, enhance-issue, review-issue, discussions
17
+ required: true
18
+ config:
19
+ description: "Path to agentic-lib.yml configuration file"
20
+ required: false
21
+ default: ".github/agentic-lib/agents/agentic-lib.yml"
22
+ instructions:
23
+ description: "Path to agent prompt/instructions file (.md)"
24
+ required: false
25
+ issue-number:
26
+ description: "GitHub issue number (when task involves an issue)"
27
+ required: false
28
+ pr-number:
29
+ description: "GitHub PR number (when task involves a pull request)"
30
+ required: false
31
+ writable-paths:
32
+ description: "Semicolon-separated paths the agent may write to (overrides config)"
33
+ required: false
34
+ test-command:
35
+ description: "Command to validate changes"
36
+ required: false
37
+ default: "npm test"
38
+ discussion-url:
39
+ description: "GitHub Discussion URL (for discussions task)"
40
+ required: false
41
+ model:
42
+ description: "Copilot SDK model to use"
43
+ required: false
44
+ default: "claude-sonnet-4"
45
+
46
+ outputs:
47
+ result:
48
+ description: 'The result of the agentic task (e.g. "pr-created", "code-fixed", "nop")'
49
+ pr-number:
50
+ description: "PR number created or modified (if applicable)"
51
+ tokens-used:
52
+ description: "Total tokens consumed by the Copilot SDK"
53
+ model:
54
+ description: "Model used for the completion"
55
+
56
+ runs:
57
+ using: "node24"
58
+ main: "index.js"
@@ -0,0 +1,153 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ // Copyright (C) 2025-2026 Polycode Limited
3
+ // config-loader.js — Parse agentic-lib.toml and resolve paths
4
+ //
5
+ // TOML-only configuration. The config file is required.
6
+ // All defaults are defined here in one place.
7
+
8
+ import { readFileSync, existsSync } from "fs";
9
+ import { dirname, join } from "path";
10
+ import { parse as parseToml } from "smol-toml";
11
+
12
+ /**
13
+ * @typedef {Object} PathConfig
14
+ * @property {string} path - The filesystem path
15
+ * @property {string[]} permissions - Access permissions (e.g. ['write'])
16
+ * @property {number} [limit] - Maximum number of files allowed
17
+ */
18
+
19
+ /**
20
+ * @typedef {Object} AgenticConfig
21
+ * @property {string} schedule - Schedule identifier
22
+ * @property {Object<string, PathConfig>} paths - Mapped paths with permissions
23
+ * @property {string} buildScript - Build command
24
+ * @property {string} testScript - Test command
25
+ * @property {string} mainScript - Main entry command
26
+ * @property {number} featureDevelopmentIssuesWipLimit - Max concurrent feature issues
27
+ * @property {number} maintenanceIssuesWipLimit - Max concurrent maintenance issues
28
+ * @property {number} attemptsPerBranch - Max attempts per branch
29
+ * @property {number} attemptsPerIssue - Max attempts per issue
30
+ * @property {Object} seeding - Seed file configuration
31
+ * @property {Object} intentionBot - Bot configuration
32
+ * @property {boolean} tdd - Whether TDD mode is enabled
33
+ * @property {string[]} writablePaths - All paths with write permission
34
+ * @property {string[]} readOnlyPaths - All paths without write permission
35
+ */
36
+
37
+ // Keys whose paths are writable by agents
38
+ const WRITABLE_KEYS = ["source", "tests", "features", "dependencies", "docs", "readme"];
39
+
40
+ // Default paths — every key that task handlers might access
41
+ const PATH_DEFAULTS = {
42
+ mission: "MISSION.md",
43
+ source: "src/lib/",
44
+ tests: "tests/unit/",
45
+ features: ".github/agentic-lib/features/",
46
+ docs: "docs/",
47
+ readme: "README.md",
48
+ dependencies: "package.json",
49
+ library: "library/",
50
+ librarySources: "SOURCES.md",
51
+ contributing: "CONTRIBUTING.md",
52
+ };
53
+
54
+ // Default limits for path-specific constraints
55
+ const LIMIT_DEFAULTS = {
56
+ features: 4,
57
+ library: 32,
58
+ };
59
+
60
+ /**
61
+ * Load configuration from agentic-lib.toml.
62
+ *
63
+ * If configPath ends in .toml, it is used directly.
64
+ * Otherwise, the project root is derived (3 levels up from configPath)
65
+ * and agentic-lib.toml is loaded from there.
66
+ *
67
+ * @param {string} configPath - Path to config file or YAML path (for project root derivation)
68
+ * @returns {AgenticConfig} Parsed configuration object
69
+ * @throws {Error} If no TOML config file is found
70
+ */
71
+ export function loadConfig(configPath) {
72
+ let tomlPath;
73
+ if (configPath.endsWith(".toml")) {
74
+ tomlPath = configPath;
75
+ } else {
76
+ const configDir = dirname(configPath);
77
+ const projectRoot = join(configDir, "..", "..", "..");
78
+ tomlPath = join(projectRoot, "agentic-lib.toml");
79
+ }
80
+
81
+ if (!existsSync(tomlPath)) {
82
+ throw new Error(`Config file not found: ${tomlPath}. Create agentic-lib.toml in the project root.`);
83
+ }
84
+
85
+ const toml = parseToml(readFileSync(tomlPath, "utf8"));
86
+
87
+ // Merge TOML paths with defaults, normalising library-sources → librarySources
88
+ const rawPaths = { ...toml.paths };
89
+ if (rawPaths["library-sources"]) {
90
+ rawPaths.librarySources = rawPaths["library-sources"];
91
+ delete rawPaths["library-sources"];
92
+ }
93
+ const mergedPaths = { ...PATH_DEFAULTS, ...rawPaths };
94
+
95
+ // Build path objects with permissions
96
+ const paths = {};
97
+ const writablePaths = [];
98
+ const readOnlyPaths = [];
99
+
100
+ for (const [key, value] of Object.entries(mergedPaths)) {
101
+ const isWritable = WRITABLE_KEYS.includes(key);
102
+ paths[key] = { path: value, permissions: isWritable ? ["write"] : [] };
103
+ if (isWritable) {
104
+ writablePaths.push(value);
105
+ } else {
106
+ readOnlyPaths.push(value);
107
+ }
108
+ }
109
+
110
+ // Apply limits from [limits] section or use defaults
111
+ const limits = toml.limits || {};
112
+ paths.features.limit = limits["features-limit"] || LIMIT_DEFAULTS.features;
113
+ paths.library.limit = limits["library-limit"] || LIMIT_DEFAULTS.library;
114
+
115
+ const execution = toml.execution || {};
116
+ const bot = toml.bot || {};
117
+
118
+ return {
119
+ schedule: toml.schedule?.tier || "schedule-1",
120
+ paths,
121
+ buildScript: execution.build || "npm run build",
122
+ testScript: execution.test || "npm test",
123
+ mainScript: execution.start || "npm run start",
124
+ featureDevelopmentIssuesWipLimit: limits["feature-issues"] || 2,
125
+ maintenanceIssuesWipLimit: limits["maintenance-issues"] || 1,
126
+ attemptsPerBranch: limits["attempts-per-branch"] || 3,
127
+ attemptsPerIssue: limits["attempts-per-issue"] || 2,
128
+ seeding: toml.seeding || {},
129
+ intentionBot: {
130
+ intentionFilepath: bot["log-file"] || "intentïon.md",
131
+ },
132
+ tdd: toml.tdd === true,
133
+ writablePaths,
134
+ readOnlyPaths,
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Get the writable paths from config, optionally overridden by an input string.
140
+ *
141
+ * @param {AgenticConfig} config - Parsed config
142
+ * @param {string} [override] - Semicolon-separated override paths
143
+ * @returns {string[]} Array of writable paths
144
+ */
145
+ export function getWritablePaths(config, override) {
146
+ if (override) {
147
+ return override
148
+ .split(";")
149
+ .map((p) => p.trim())
150
+ .filter(Boolean);
151
+ }
152
+ return config.writablePaths;
153
+ }
@@ -0,0 +1,170 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ // Copyright (C) 2025-2026 Polycode Limited
3
+ // copilot.js — Shared utilities for Copilot SDK task handlers
4
+ //
5
+ // Extracts repeated patterns from the 8 task handlers into reusable functions.
6
+
7
+ import { CopilotClient, approveAll } from "@github/copilot-sdk";
8
+ import { readFileSync, readdirSync, existsSync } from "fs";
9
+ import { createAgentTools } from "./tools.js";
10
+ import * as core from "@actions/core";
11
+
12
+ /**
13
+ * Build the CopilotClient options for authentication.
14
+ *
15
+ * Auth strategy (in order of preference):
16
+ * 1. COPILOT_GITHUB_TOKEN env var → override GITHUB_TOKEN/GH_TOKEN in subprocess env
17
+ * so the Copilot CLI's auto-login finds the PAT instead of the Actions token.
18
+ * 2. Fall back to whatever auth is available (GITHUB_TOKEN, gh CLI login, etc.)
19
+ *
20
+ * Note: Passing githubToken directly to CopilotClient causes 400 on models.list.
21
+ * Instead we override the env vars so the CLI subprocess picks up the right token
22
+ * via its auto-login flow (useLoggedInUser: true).
23
+ *
24
+ * @param {string} [githubToken] - Optional token; falls back to COPILOT_GITHUB_TOKEN env var.
25
+ */
26
+ export function buildClientOptions(githubToken) {
27
+ const copilotToken = githubToken || process.env.COPILOT_GITHUB_TOKEN;
28
+ if (!copilotToken) {
29
+ throw new Error("COPILOT_GITHUB_TOKEN is required. Set it as a repository secret.");
30
+ }
31
+ core.info("[copilot] COPILOT_GITHUB_TOKEN found — overriding subprocess env");
32
+ const env = { ...process.env };
33
+ // Override both GITHUB_TOKEN and GH_TOKEN so the Copilot CLI
34
+ // subprocess uses the Copilot PAT for its auto-login flow
35
+ env.GITHUB_TOKEN = copilotToken;
36
+ env.GH_TOKEN = copilotToken;
37
+ return { env };
38
+ }
39
+
40
+ /**
41
+ * Run a Copilot SDK session and return the response.
42
+ * Handles the full lifecycle: create client → create session → send → stop.
43
+ *
44
+ * @param {Object} options
45
+ * @param {string} options.model - Copilot SDK model name
46
+ * @param {string} options.systemMessage - System message content
47
+ * @param {string} options.prompt - The prompt to send
48
+ * @param {string[]} options.writablePaths - Paths the agent may modify
49
+ * @param {string} [options.githubToken] - Optional token; falls back to COPILOT_GITHUB_TOKEN env var.
50
+ * @returns {Promise<{content: string, tokensUsed: number}>}
51
+ */
52
+ export async function runCopilotTask({ model, systemMessage, prompt, writablePaths, githubToken }) {
53
+ core.info(
54
+ `[copilot] Creating client (model=${model}, promptLen=${prompt.length}, writablePaths=${writablePaths.length})`,
55
+ );
56
+
57
+ const clientOptions = buildClientOptions(githubToken);
58
+ const client = new CopilotClient(clientOptions);
59
+
60
+ try {
61
+ core.info("[copilot] Creating session...");
62
+ const session = await client.createSession({
63
+ model,
64
+ systemMessage: { content: systemMessage },
65
+ tools: createAgentTools(writablePaths),
66
+ onPermissionRequest: approveAll,
67
+ workingDirectory: process.cwd(),
68
+ });
69
+ core.info(`[copilot] Session created: ${session.sessionId}`);
70
+
71
+ // Check auth status now that client is connected
72
+ try {
73
+ const authStatus = await client.getAuthStatus();
74
+ core.info(`[copilot] Auth status: ${JSON.stringify(authStatus)}`);
75
+ } catch (authErr) {
76
+ core.warning(`[copilot] Auth check failed: ${authErr.message}`);
77
+ }
78
+
79
+ // Register wildcard event handler for ALL events
80
+ session.on((event) => {
81
+ const eventType = event?.type || "unknown";
82
+ if (eventType === "assistant.message") {
83
+ const preview = event?.data?.content?.substring(0, 100) || "(no content)";
84
+ core.info(`[copilot] event=${eventType}: ${preview}...`);
85
+ } else if (eventType === "session.idle") {
86
+ core.info(`[copilot] event=${eventType}`);
87
+ } else if (eventType === "session.error") {
88
+ core.error(`[copilot] event=${eventType}: ${JSON.stringify(event?.data || event)}`);
89
+ } else {
90
+ core.info(`[copilot] event=${eventType}: ${JSON.stringify(event?.data || event).substring(0, 200)}`);
91
+ }
92
+ });
93
+
94
+ core.info("[copilot] Sending prompt and waiting for idle...");
95
+ const response = await session.sendAndWait({ prompt }, 300000);
96
+ core.info(`[copilot] sendAndWait resolved`);
97
+ const tokensUsed = response?.data?.usage?.totalTokens || 0;
98
+ const content = response?.data?.content || "";
99
+
100
+ return { content, tokensUsed };
101
+ } finally {
102
+ await client.stop();
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Read a file, returning empty string on failure. For optional context files.
108
+ *
109
+ * @param {string} filePath - Path to read
110
+ * @param {number} [limit] - Maximum characters to return
111
+ * @returns {string}
112
+ */
113
+ export function readOptionalFile(filePath, limit) {
114
+ try {
115
+ const content = readFileSync(filePath, "utf8");
116
+ return limit ? content.substring(0, limit) : content;
117
+ } catch (err) {
118
+ core.debug(`[readOptionalFile] ${filePath}: ${err.message}`);
119
+ return "";
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Scan a directory for files matching an extension, returning name+content pairs.
125
+ *
126
+ * @param {string} dirPath - Directory to scan
127
+ * @param {string|string[]} extensions - File extension(s) to match (e.g. '.md', ['.js', '.ts'])
128
+ * @param {Object} [options]
129
+ * @param {number} [options.fileLimit=10] - Max files to return
130
+ * @param {number} [options.contentLimit] - Max chars per file content
131
+ * @param {boolean} [options.recursive=false] - Scan recursively
132
+ * @returns {Array<{name: string, content: string}>}
133
+ */
134
+ export function scanDirectory(dirPath, extensions, options = {}) {
135
+ const { fileLimit = 10, contentLimit, recursive = false } = options;
136
+ const exts = Array.isArray(extensions) ? extensions : [extensions];
137
+
138
+ if (!existsSync(dirPath)) return [];
139
+
140
+ return readdirSync(dirPath, recursive ? { recursive: true } : undefined)
141
+ .filter((f) => exts.some((ext) => f.endsWith(ext)))
142
+ .slice(0, fileLimit)
143
+ .map((f) => {
144
+ try {
145
+ const content = readFileSync(`${dirPath}${f}`, "utf8");
146
+ return { name: f, content: contentLimit ? content.substring(0, contentLimit) : content };
147
+ } catch (err) {
148
+ core.debug(`[scanDirectory] ${dirPath}${f}: ${err.message}`);
149
+ return { name: f, content: "" };
150
+ }
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Format the writable/read-only paths section for a prompt.
156
+ *
157
+ * @param {string[]} writablePaths
158
+ * @param {string[]} [readOnlyPaths=[]]
159
+ * @returns {string}
160
+ */
161
+ export function formatPathsSection(writablePaths, readOnlyPaths = []) {
162
+ return [
163
+ "## File Paths",
164
+ "### Writable (you may modify these)",
165
+ writablePaths.length > 0 ? writablePaths.map((p) => `- ${p}`).join("\n") : "- (none)",
166
+ "",
167
+ "### Read-Only (for context only, do NOT modify)",
168
+ readOnlyPaths.length > 0 ? readOnlyPaths.map((p) => `- ${p}`).join("\n") : "- (none)",
169
+ ].join("\n");
170
+ }
@@ -0,0 +1,118 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ // Copyright (C) 2025-2026 Polycode Limited
3
+ // index.js — agentic-step GitHub Action entry point
4
+ //
5
+ // Parses inputs, loads config, runs the appropriate task via the Copilot SDK,
6
+ // and sets outputs for downstream workflow steps.
7
+
8
+ import * as core from "@actions/core";
9
+ import * as github from "@actions/github";
10
+ import { loadConfig, getWritablePaths } from "./config-loader.js";
11
+ import { logActivity } from "./logging.js";
12
+ import { readFileSync } from "fs";
13
+
14
+ // Task implementations
15
+ import { resolveIssue } from "./tasks/resolve-issue.js";
16
+ import { fixCode } from "./tasks/fix-code.js";
17
+ import { transform } from "./tasks/transform.js";
18
+ import { maintainFeatures } from "./tasks/maintain-features.js";
19
+ import { maintainLibrary } from "./tasks/maintain-library.js";
20
+ import { enhanceIssue } from "./tasks/enhance-issue.js";
21
+ import { reviewIssue } from "./tasks/review-issue.js";
22
+ import { discussions } from "./tasks/discussions.js";
23
+
24
+ const TASKS = {
25
+ "resolve-issue": resolveIssue,
26
+ "fix-code": fixCode,
27
+ "transform": transform,
28
+ "maintain-features": maintainFeatures,
29
+ "maintain-library": maintainLibrary,
30
+ "enhance-issue": enhanceIssue,
31
+ "review-issue": reviewIssue,
32
+ "discussions": discussions,
33
+ };
34
+
35
+ async function run() {
36
+ try {
37
+ // Parse inputs
38
+ const task = core.getInput("task", { required: true });
39
+ const configPath = core.getInput("config");
40
+ const instructionsPath = core.getInput("instructions");
41
+ const issueNumber = core.getInput("issue-number");
42
+ const prNumber = core.getInput("pr-number");
43
+ const writablePathsOverride = core.getInput("writable-paths");
44
+ const testCommand = core.getInput("test-command");
45
+ const discussionUrl = core.getInput("discussion-url");
46
+ const model = core.getInput("model");
47
+
48
+ core.info(`agentic-step: task=${task}, model=${model}`);
49
+
50
+ // Load config
51
+ const config = loadConfig(configPath);
52
+ const writablePaths = getWritablePaths(config, writablePathsOverride);
53
+
54
+ // Load instructions if provided
55
+ let instructions = "";
56
+ if (instructionsPath) {
57
+ try {
58
+ instructions = readFileSync(instructionsPath, "utf8");
59
+ } catch (err) {
60
+ core.warning(`Could not read instructions file: ${instructionsPath}: ${err.message}`);
61
+ }
62
+ }
63
+
64
+ // Look up the task handler
65
+ const handler = TASKS[task];
66
+ if (!handler) {
67
+ throw new Error(`Unknown task: ${task}. Available tasks: ${Object.keys(TASKS).join(", ")}`);
68
+ }
69
+
70
+ // Build context for the task
71
+ const context = {
72
+ task,
73
+ config,
74
+ instructions,
75
+ issueNumber,
76
+ prNumber,
77
+ writablePaths,
78
+ testCommand,
79
+ discussionUrl,
80
+ model,
81
+ octokit: github.getOctokit(process.env.GITHUB_TOKEN),
82
+ repo: github.context.repo,
83
+ github: github.context,
84
+ };
85
+
86
+ // Run the task
87
+ const result = await handler(context);
88
+
89
+ // Set outputs
90
+ core.setOutput("result", result.outcome || "completed");
91
+ if (result.prNumber) core.setOutput("pr-number", String(result.prNumber));
92
+ if (result.tokensUsed) core.setOutput("tokens-used", String(result.tokensUsed));
93
+ if (result.model) core.setOutput("model", result.model);
94
+
95
+ // Log to intentïon.md
96
+ const intentionFilepath = config.intentionBot?.intentionFilepath;
97
+ if (intentionFilepath) {
98
+ logActivity({
99
+ filepath: intentionFilepath,
100
+ task,
101
+ outcome: result.outcome || "completed",
102
+ issueNumber,
103
+ prNumber: result.prNumber,
104
+ commitUrl: result.commitUrl,
105
+ tokensUsed: result.tokensUsed,
106
+ model: result.model || model,
107
+ details: result.details,
108
+ workflowUrl: `${process.env.GITHUB_SERVER_URL}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}`,
109
+ });
110
+ }
111
+
112
+ core.info(`agentic-step completed: outcome=${result.outcome}`);
113
+ } catch (error) {
114
+ core.setFailed(`agentic-step failed: ${error.message}`);
115
+ }
116
+ }
117
+
118
+ run();
@@ -0,0 +1,88 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ // Copyright (C) 2025-2026 Polycode Limited
3
+ // logging.js — intentïon.md activity log writer
4
+ //
5
+ // Appends structured entries to the intentïon.md activity log,
6
+ // including commit URLs and safety-check outcomes.
7
+
8
+ import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs";
9
+ import { dirname } from "path";
10
+ import * as core from "@actions/core";
11
+
12
+ /**
13
+ * Log an activity to the intentïon.md file.
14
+ *
15
+ * @param {Object} options
16
+ * @param {string} options.filepath - Path to the intentïon.md file
17
+ * @param {string} options.task - The task that was performed
18
+ * @param {string} options.outcome - The outcome (e.g. 'pr-created', 'nop', 'error')
19
+ * @param {string} [options.issueNumber] - Related issue number
20
+ * @param {string} [options.prNumber] - Related PR number
21
+ * @param {string} [options.commitUrl] - URL to the commit
22
+ * @param {number} [options.tokensUsed] - Tokens consumed
23
+ * @param {string} [options.model] - Model used
24
+ * @param {string} [options.details] - Additional details
25
+ * @param {string} [options.workflowUrl] - URL to the workflow run
26
+ */
27
+ export function logActivity({
28
+ filepath,
29
+ task,
30
+ outcome,
31
+ issueNumber,
32
+ prNumber,
33
+ commitUrl,
34
+ tokensUsed,
35
+ model,
36
+ details,
37
+ workflowUrl,
38
+ }) {
39
+ const dir = dirname(filepath);
40
+ if (!existsSync(dir)) {
41
+ mkdirSync(dir, { recursive: true });
42
+ }
43
+
44
+ const isoDate = new Date().toISOString();
45
+ const parts = [`\n## ${task} at ${isoDate}`, "", `**Outcome:** ${outcome}`];
46
+
47
+ if (issueNumber) parts.push(`**Issue:** #${issueNumber}`);
48
+ if (prNumber) parts.push(`**PR:** #${prNumber}`);
49
+ if (commitUrl) parts.push(`**Commit:** [${commitUrl}](${commitUrl})`);
50
+ if (model) parts.push(`**Model:** ${model}`);
51
+ if (tokensUsed !== undefined) parts.push(`**Tokens:** ${tokensUsed}`);
52
+ if (workflowUrl) parts.push(`**Workflow:** [${workflowUrl}](${workflowUrl})`);
53
+ if (details) {
54
+ parts.push("");
55
+ parts.push(details);
56
+ }
57
+ parts.push("");
58
+ parts.push("---");
59
+
60
+ const entry = parts.join("\n");
61
+
62
+ if (existsSync(filepath)) {
63
+ appendFileSync(filepath, entry);
64
+ } else {
65
+ writeFileSync(filepath, `# intentïon Activity Log\n${entry}`);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Log a safety check outcome to the GitHub Actions log.
71
+ *
72
+ * @param {string} checkName - The name of the safety check (e.g. 'attempt-limit', 'wip-limit', 'issue-resolved')
73
+ * @param {boolean} passed - Whether the check passed (true = allowed to proceed)
74
+ * @param {Object} [details] - Additional details about the check
75
+ */
76
+ export function logSafetyCheck(checkName, passed, details = {}) {
77
+ const detailStr = Object.entries(details)
78
+ .map(([k, v]) => `${k}=${v}`)
79
+ .join(", ");
80
+ const status = passed ? "PASSED" : "BLOCKED";
81
+ const suffix = detailStr ? ` (${detailStr})` : "";
82
+ const message = `Safety check [${checkName}]: ${status}${suffix}`;
83
+ if (passed) {
84
+ core.info(message);
85
+ } else {
86
+ core.warning(message);
87
+ }
88
+ }