@infinisys/is-devops 0.0.0-stage → 0.1.1
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/README.md +75 -2
- package/client.js +1667 -0
- package/cordis.patch.yml +4 -0
- package/icon.svg +13 -0
- package/index.js +955 -0
- package/lib/bot-identity.js +115 -0
- package/lib/commit-scope.js +249 -0
- package/lib/git-exec.js +135 -0
- package/lib/git-parse.js +176 -0
- package/lib/git-repository.js +1364 -0
- package/lib/git-tool.js +223 -0
- package/lib/gitfox-api.js +600 -0
- package/lib/project-discovery.js +62 -0
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +48 -4
package/lib/git-tool.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `infinity_git` agent tool.
|
|
3
|
+
*
|
|
4
|
+
* One compact tool with read-only inspection (`status`, `diff`, `log`) and the
|
|
5
|
+
* three mutation steps that must stay auditable (`stage`, `commit`, `push`).
|
|
6
|
+
* It is the only supported way for Team Collaboration Mode Agents to touch
|
|
7
|
+
* Git: `is-fabric-team` denies direct Git invocations through `bash`.
|
|
8
|
+
*/
|
|
9
|
+
import { GitFoxError } from './gitfox-api.js';
|
|
10
|
+
|
|
11
|
+
/** Model-facing description. */
|
|
12
|
+
const TOOL_DESCRIPTION =
|
|
13
|
+
'Inspect and change the local Git repository behind the session workspace. Read actions: status, diff, log. Mutation actions: stage, unstage, switch, commit, fetch, rebase, rebase_continue, rebase_abort, push, delete_branch, prune_worktrees. Staging is recorded per seat: a commit contains exactly the paths this seat staged, so work staged by another seat and edits made after staging cannot ride along. "delete_branch" removes a local branch (never the checked-out one; remote branches go through infinity_devops) and "prune_worktrees" drops metadata for worktrees whose directory is gone. "switch" checks out a branch (create it with "create" and start it at "from"); uncommitted changes are carried across the switch, so never ask the user to run Git themselves. A rebase runs locally on the checked-out branch; when it stops on conflicts, resolve the listed files, stage them and call rebase_continue (or rebase_abort), then push. Commits are authored as infinsys-<role>; fetch and push authenticate to GitFox with the saved PAT/OAuth2 token and use the "namespace/name" project. No shell is involved and all paths must stay inside the workspace.';
|
|
14
|
+
|
|
15
|
+
/** JSON-schema parameters of `infinity_git`. */
|
|
16
|
+
const TOOL_PARAMETERS = {
|
|
17
|
+
type: 'object',
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
properties: {
|
|
20
|
+
action: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
enum: [
|
|
23
|
+
'status',
|
|
24
|
+
'diff',
|
|
25
|
+
'log',
|
|
26
|
+
'stage',
|
|
27
|
+
'unstage',
|
|
28
|
+
'switch',
|
|
29
|
+
'commit',
|
|
30
|
+
'fetch',
|
|
31
|
+
'rebase',
|
|
32
|
+
'rebase_continue',
|
|
33
|
+
'rebase_abort',
|
|
34
|
+
'push',
|
|
35
|
+
'delete_branch',
|
|
36
|
+
'prune_worktrees',
|
|
37
|
+
],
|
|
38
|
+
description: 'Git operation. Read actions are safe; mutation actions are audited.',
|
|
39
|
+
},
|
|
40
|
+
paths: {
|
|
41
|
+
type: 'array',
|
|
42
|
+
items: { type: 'string' },
|
|
43
|
+
description:
|
|
44
|
+
'Workspace-relative paths: stage and unstage take the paths to add or drop, commit takes exactly the paths to include (the rest of this seat\'s staging stays for a later commit), diff and log take path filters. Never absolute and never .git.',
|
|
45
|
+
},
|
|
46
|
+
message: { type: 'string', description: 'Commit message for action "commit".' },
|
|
47
|
+
project: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'GitFox project in "namespace/name" form; required for "push".',
|
|
50
|
+
},
|
|
51
|
+
branch: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description:
|
|
54
|
+
'Branch to check out, create or push; for "fetch" the branch to fetch, for "push" the remote branch (defaults to the current branch).',
|
|
55
|
+
},
|
|
56
|
+
create: {
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
description: 'Create the branch during action "switch" instead of requiring it to exist.',
|
|
59
|
+
},
|
|
60
|
+
from: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description:
|
|
63
|
+
'Start point for a created branch ("origin/main", a tag, a SHA or a branch); defaults to the current HEAD.',
|
|
64
|
+
},
|
|
65
|
+
onto: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description:
|
|
68
|
+
'Commit-ish to rebase onto ("origin/main", a tag or a SHA). Omit to rebase onto the upstream of the checked-out branch.',
|
|
69
|
+
},
|
|
70
|
+
force: {
|
|
71
|
+
type: 'boolean',
|
|
72
|
+
description:
|
|
73
|
+
'Use --force-with-lease for "push", or force the deletion of an unmerged local branch for "delete_branch"; never allowed on main/master.',
|
|
74
|
+
},
|
|
75
|
+
staged: { type: 'boolean', description: 'Diff the index instead of the working tree.' },
|
|
76
|
+
stat: { type: 'boolean', description: 'Return a diffstat instead of a patch.' },
|
|
77
|
+
limit: {
|
|
78
|
+
type: 'integer',
|
|
79
|
+
minimum: 1,
|
|
80
|
+
maximum: 100,
|
|
81
|
+
description: 'Number of commits for "log"; defaults to 20.',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ['action'],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {unknown} value - canonical tool value.
|
|
89
|
+
* @returns {string} model-facing text bounded to 24k characters.
|
|
90
|
+
*/
|
|
91
|
+
function formatValue(value) {
|
|
92
|
+
const text = JSON.stringify(value, null, 2) ?? 'null';
|
|
93
|
+
return text.length <= 24_000 ? text : `${text.slice(0, 24_000)}\n… (truncated)`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve the repository root for one tool call.
|
|
98
|
+
*
|
|
99
|
+
* The header keeps the directory the session was opened with, so a worker that
|
|
100
|
+
* moved into a worktree still reports the project root there. The live
|
|
101
|
+
* directory (`workingDirectory` service) is what the call is actually running
|
|
102
|
+
* in, which is the repository a lane's commits belong to.
|
|
103
|
+
*
|
|
104
|
+
* @param {{ agent?: { session?: { header?: { cwd?: unknown } } } }} [exec] - execution context.
|
|
105
|
+
* @param {((agent: object) => string | null) | undefined} directoryOf - live directory accessor.
|
|
106
|
+
* @returns {string} absolute session workspace.
|
|
107
|
+
*/
|
|
108
|
+
function workspaceRootFor(exec, directoryOf) {
|
|
109
|
+
try {
|
|
110
|
+
const live = directoryOf?.(exec?.agent);
|
|
111
|
+
if (typeof live === 'string' && live !== '') return live;
|
|
112
|
+
} catch {
|
|
113
|
+
// A Host without the service, or one that cannot answer yet, keeps the header.
|
|
114
|
+
}
|
|
115
|
+
const cwd = exec?.agent?.session?.header?.cwd;
|
|
116
|
+
if (typeof cwd === 'string' && cwd !== '') return cwd;
|
|
117
|
+
return process.cwd();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validate and shape one action before it reaches the repository service.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} action - action name.
|
|
124
|
+
* @param {Record<string, any>} args - tool arguments.
|
|
125
|
+
* @returns {object} options for the repository service.
|
|
126
|
+
*/
|
|
127
|
+
function actionOptions(action, args) {
|
|
128
|
+
if (action === 'push' || action === 'fetch') {
|
|
129
|
+
if (typeof args.project !== 'string' || args.project.trim() === '') {
|
|
130
|
+
throw new GitFoxError(`action "${action}" requires "project"`, { code: 'invalid-arguments' });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (action === 'commit' && (typeof args.message !== 'string' || args.message.trim() === '')) {
|
|
134
|
+
throw new GitFoxError('action "commit" requires "message"', { code: 'invalid-arguments' });
|
|
135
|
+
}
|
|
136
|
+
if (action === 'switch' && (typeof args.branch !== 'string' || args.branch.trim() === '')) {
|
|
137
|
+
throw new GitFoxError('action "switch" requires "branch"', { code: 'invalid-arguments' });
|
|
138
|
+
}
|
|
139
|
+
if (action === 'delete_branch' && (typeof args.branch !== 'string' || args.branch.trim() === '')) {
|
|
140
|
+
throw new GitFoxError('action "delete_branch" requires "branch"', { code: 'invalid-arguments' });
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
project: args.project,
|
|
144
|
+
branch: args.branch,
|
|
145
|
+
create: args.create === true,
|
|
146
|
+
from: args.from,
|
|
147
|
+
force: args.force === true,
|
|
148
|
+
onto: args.onto,
|
|
149
|
+
message: args.message,
|
|
150
|
+
paths: args.paths,
|
|
151
|
+
staged: args.staged === true,
|
|
152
|
+
stat: args.stat === true,
|
|
153
|
+
limit: args.limit,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Run one action and return the canonical tool value.
|
|
159
|
+
*
|
|
160
|
+
* @param {object} git - repository service.
|
|
161
|
+
* @param {string} action - action name.
|
|
162
|
+
* @param {Record<string, any>} args - tool arguments.
|
|
163
|
+
* @param {{ workspaceRoot: string, signal?: AbortSignal, actor: object }} context - resolved invocation.
|
|
164
|
+
* @returns {Promise<object>} canonical result.
|
|
165
|
+
*/
|
|
166
|
+
async function executeAction(git, action, args, context) {
|
|
167
|
+
const options = { ...actionOptions(action, args), ...context };
|
|
168
|
+
if (typeof git[action] !== 'function') {
|
|
169
|
+
throw new Error(`invalid-arguments: unknown action "${action}"`);
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const result = await git[action](context.actor, options);
|
|
173
|
+
return action === 'push'
|
|
174
|
+
? { action, project: args.project, result }
|
|
175
|
+
: { action, result };
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error instanceof GitFoxError) {
|
|
178
|
+
throw new Error(`${error.code}: ${error.message}`, { cause: error });
|
|
179
|
+
}
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Build the `infinity_git` tool definition.
|
|
186
|
+
*
|
|
187
|
+
* @param {{ git: object, resolveActor: (agent?: object) => Promise<object> }} deps - wiring.
|
|
188
|
+
* @returns {object} tool definition.
|
|
189
|
+
*/
|
|
190
|
+
export function createGitTool({ git, resolveActor, directoryOf }) {
|
|
191
|
+
return {
|
|
192
|
+
name: 'infinity_git',
|
|
193
|
+
description: TOOL_DESCRIPTION,
|
|
194
|
+
parameters: TOOL_PARAMETERS,
|
|
195
|
+
output: {
|
|
196
|
+
schema: { type: 'object', additionalProperties: true },
|
|
197
|
+
/**
|
|
198
|
+
* @param {unknown} _args - validated arguments.
|
|
199
|
+
* @param {unknown} value - canonical value.
|
|
200
|
+
* @returns {Array<{ type: 'text', text: string }>} model-facing content.
|
|
201
|
+
*/
|
|
202
|
+
render(_args, value) {
|
|
203
|
+
return [{ type: 'text', text: formatValue(value) }];
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
/**
|
|
207
|
+
* @param {Record<string, any>} args - validated arguments.
|
|
208
|
+
* @param {{ signal?: AbortSignal, agent?: object }} exec - execution context.
|
|
209
|
+
* @returns {Promise<object>} canonical value.
|
|
210
|
+
*/
|
|
211
|
+
async execute(args, exec) {
|
|
212
|
+
exec.signal?.throwIfAborted();
|
|
213
|
+
const actor = await resolveActor(exec.agent);
|
|
214
|
+
const result = await executeAction(git, args.action, args, {
|
|
215
|
+
workspaceRoot: workspaceRootFor(exec, directoryOf),
|
|
216
|
+
signal: exec.signal,
|
|
217
|
+
actor,
|
|
218
|
+
});
|
|
219
|
+
exec.signal?.throwIfAborted();
|
|
220
|
+
return result;
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|