@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.
- package/LICENSE +674 -0
- package/README.md +323 -0
- package/bin/agentic-lib.js +765 -0
- package/package.json +102 -0
- package/src/actions/agentic-step/action.yml +58 -0
- package/src/actions/agentic-step/config-loader.js +153 -0
- package/src/actions/agentic-step/copilot.js +170 -0
- package/src/actions/agentic-step/index.js +118 -0
- package/src/actions/agentic-step/logging.js +88 -0
- package/src/actions/agentic-step/package-lock.json +1891 -0
- package/src/actions/agentic-step/package.json +29 -0
- package/src/actions/agentic-step/safety.js +103 -0
- package/src/actions/agentic-step/tasks/discussions.js +141 -0
- package/src/actions/agentic-step/tasks/enhance-issue.js +102 -0
- package/src/actions/agentic-step/tasks/fix-code.js +71 -0
- package/src/actions/agentic-step/tasks/maintain-features.js +79 -0
- package/src/actions/agentic-step/tasks/maintain-library.js +67 -0
- package/src/actions/agentic-step/tasks/resolve-issue.js +98 -0
- package/src/actions/agentic-step/tasks/review-issue.js +121 -0
- package/src/actions/agentic-step/tasks/transform.js +213 -0
- package/src/actions/agentic-step/tools.js +142 -0
- package/src/actions/commit-if-changed/action.yml +39 -0
- package/src/actions/setup-npmrc/action.yml +38 -0
- package/src/agents/agent-apply-fix.md +13 -0
- package/src/agents/agent-discussion-bot.md +35 -0
- package/src/agents/agent-issue-resolution.md +13 -0
- package/src/agents/agent-maintain-features.md +29 -0
- package/src/agents/agent-maintain-library.md +31 -0
- package/src/agents/agent-ready-issue.md +13 -0
- package/src/agents/agent-review-issue.md +2 -0
- package/src/agents/agentic-lib.yml +68 -0
- package/src/scripts/accept-release.sh +29 -0
- package/src/scripts/activate-schedule.sh +41 -0
- package/src/scripts/clean.sh +21 -0
- package/src/scripts/generate-library-index.js +143 -0
- package/src/scripts/initialise.sh +39 -0
- package/src/scripts/md-to-html.js +77 -0
- package/src/scripts/update.sh +19 -0
- package/src/seeds/test.yml +33 -0
- package/src/seeds/zero-MISSION.md +7 -0
- package/src/seeds/zero-README.md +14 -0
- package/src/seeds/zero-agentic-lib.toml +32 -0
- package/src/seeds/zero-main.js +15 -0
- package/src/seeds/zero-main.test.js +11 -0
- package/src/seeds/zero-package.json +26 -0
- package/src/workflows/agent-discussions-bot.yml +78 -0
- package/src/workflows/agent-flow-fix-code.yml +98 -0
- package/src/workflows/agent-flow-maintain.yml +114 -0
- package/src/workflows/agent-flow-review.yml +99 -0
- package/src/workflows/agent-flow-transform.yml +82 -0
- package/src/workflows/agent-supervisor.yml +85 -0
- package/src/workflows/ci-automerge.yml +544 -0
- 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
|
+
}
|