@google/jules-merge 0.0.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 +201 -0
- package/dist/cli/check-conflicts.command.mjs +387 -0
- package/dist/cli/index.mjs +28 -0
- package/dist/cli/init.command.mjs +159 -0
- package/dist/core/src/activities/client.d.ts +131 -0
- package/dist/core/src/activities/summary.d.ts +22 -0
- package/dist/core/src/activities/types.d.ts +79 -0
- package/dist/core/src/api.d.ts +49 -0
- package/dist/core/src/artifacts.d.ts +105 -0
- package/dist/core/src/caching.d.ts +31 -0
- package/dist/core/src/client.d.ts +180 -0
- package/dist/core/src/errors.d.ts +97 -0
- package/dist/core/src/index.d.ts +49 -0
- package/dist/core/src/mappers.d.ts +53 -0
- package/dist/core/src/network/adapter.d.ts +47 -0
- package/dist/core/src/platform/node.d.ts +45 -0
- package/dist/core/src/platform/types.d.ts +88 -0
- package/dist/core/src/polling.d.ts +43 -0
- package/dist/core/src/query/computed.d.ts +86 -0
- package/dist/core/src/query/projection.d.ts +80 -0
- package/dist/core/src/query/schema.d.ts +124 -0
- package/dist/core/src/query/select.d.ts +21 -0
- package/dist/core/src/query/validate.d.ts +68 -0
- package/dist/core/src/session.d.ts +195 -0
- package/dist/core/src/sessions.d.ts +87 -0
- package/dist/core/src/snapshot.d.ts +46 -0
- package/dist/core/src/sources.d.ts +23 -0
- package/dist/core/src/storage/cache-info.d.ts +43 -0
- package/dist/core/src/storage/memory.d.ts +69 -0
- package/dist/core/src/storage/node-fs.d.ts +95 -0
- package/dist/core/src/storage/root.d.ts +17 -0
- package/dist/core/src/storage/types.d.ts +115 -0
- package/dist/core/src/streaming.d.ts +47 -0
- package/dist/core/src/types.d.ts +1418 -0
- package/dist/core/src/utils/page-token.d.ts +55 -0
- package/dist/core/src/utils.d.ts +27 -0
- package/dist/index.mjs +395 -0
- package/dist/merge/src/__tests__/conflicts/git-handler.test.d.ts +1 -0
- package/dist/merge/src/__tests__/conflicts/git-spec.test.d.ts +1 -0
- package/dist/merge/src/__tests__/conflicts/session-handler.test.d.ts +1 -0
- package/dist/merge/src/__tests__/conflicts/session-spec.test.d.ts +1 -0
- package/dist/merge/src/__tests__/init/init-handler.test.d.ts +1 -0
- package/dist/merge/src/__tests__/init/init-spec.test.d.ts +1 -0
- package/dist/merge/src/__tests__/init/templates.test.d.ts +1 -0
- package/dist/merge/src/__tests__/shared/git.test.d.ts +1 -0
- package/dist/merge/src/__tests__/shared/github.test.d.ts +1 -0
- package/dist/merge/src/__tests__/shared/session.test.d.ts +1 -0
- package/dist/merge/src/cli/check-conflicts.command.d.ts +24 -0
- package/dist/merge/src/cli/index.d.ts +2 -0
- package/dist/merge/src/cli/init.command.d.ts +23 -0
- package/dist/merge/src/conflicts/git-handler.d.ts +4 -0
- package/dist/merge/src/conflicts/git-spec.d.ts +43 -0
- package/dist/merge/src/conflicts/index.d.ts +4 -0
- package/dist/merge/src/conflicts/session-handler.d.ts +9 -0
- package/dist/merge/src/conflicts/session-spec.d.ts +43 -0
- package/dist/merge/src/index.d.ts +5 -0
- package/dist/merge/src/init/index.d.ts +4 -0
- package/dist/merge/src/init/init-handler.d.ts +4 -0
- package/dist/merge/src/init/init-spec.d.ts +41 -0
- package/dist/merge/src/init/templates.d.ts +10 -0
- package/dist/merge/src/mcp/index.d.ts +1 -0
- package/dist/merge/src/mcp/server.d.ts +2 -0
- package/dist/merge/src/shared/git.d.ts +17 -0
- package/dist/merge/src/shared/github.d.ts +18 -0
- package/dist/merge/src/shared/result.d.ts +19 -0
- package/dist/merge/src/shared/session.d.ts +16 -0
- package/package.json +60 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Utilities for constructing and parsing Jules API pageTokens.
|
|
18
|
+
*
|
|
19
|
+
* The Jules API uses nanosecond timestamps as pageTokens for activity pagination.
|
|
20
|
+
* This allows us to construct tokens from cached activity createTimes,
|
|
21
|
+
* enabling efficient incremental syncing without re-downloading existing activities.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Converts a Jules API pageToken back to a Date.
|
|
27
|
+
* Useful for debugging and logging.
|
|
28
|
+
*
|
|
29
|
+
* @param token - The pageToken string (nanosecond timestamp)
|
|
30
|
+
* @returns The corresponding Date object
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const date = pageTokenToDate("1704448500999999000");
|
|
35
|
+
* // Returns Date for 2024-01-05T10:05:00.999Z
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function pageTokenToDate(token: string): Date;
|
|
39
|
+
/**
|
|
40
|
+
* Checks if a session's activities are "frozen" (no new activities possible).
|
|
41
|
+
* A session is considered frozen if its last activity is older than the threshold.
|
|
42
|
+
*
|
|
43
|
+
* @param lastActivityCreateTime - The createTime of the most recent activity
|
|
44
|
+
* @param thresholdDays - Number of days after which a session is frozen (default: 30)
|
|
45
|
+
* @returns true if the session is frozen and no API call is needed
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const isFrozen = isSessionFrozen("2024-01-05T10:05:00Z");
|
|
50
|
+
* if (isFrozen) {
|
|
51
|
+
* // Skip API call - no new activities will ever appear
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function isSessionFrozen(lastActivityCreateTime: string, thresholdDays?: number): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The internal engine for jules.all()
|
|
18
|
+
*
|
|
19
|
+
* @param items - Data to process
|
|
20
|
+
* @param mapper - Async function (item) => result
|
|
21
|
+
* @param options - Configuration options
|
|
22
|
+
*/
|
|
23
|
+
export declare function pMap<T, R>(items: T[], mapper: (item: T, index: number) => Promise<R>, options?: {
|
|
24
|
+
concurrency?: number;
|
|
25
|
+
stopOnError?: boolean;
|
|
26
|
+
delayMs?: number;
|
|
27
|
+
}): Promise<R[]>;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
// src/conflicts/session-spec.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var SessionCheckInputSchema = z.object({
|
|
4
|
+
sessionId: z.string().min(1, "Session ID is required"),
|
|
5
|
+
repo: z.string().min(1).refine((s) => s.includes("/"), "Must be in owner/repo format"),
|
|
6
|
+
base: z.string().default("main")
|
|
7
|
+
});
|
|
8
|
+
var SessionCheckErrorCode = z.enum([
|
|
9
|
+
"SESSION_QUERY_FAILED",
|
|
10
|
+
"GITHUB_API_ERROR",
|
|
11
|
+
"RATE_LIMIT_EXCEEDED",
|
|
12
|
+
"UNKNOWN_ERROR"
|
|
13
|
+
]);
|
|
14
|
+
// src/shared/result.ts
|
|
15
|
+
function ok(data) {
|
|
16
|
+
return { success: true, data };
|
|
17
|
+
}
|
|
18
|
+
function fail(code, message, recoverable = false, suggestion) {
|
|
19
|
+
return { success: false, error: { code, message, recoverable, suggestion } };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/shared/session.ts
|
|
23
|
+
import { jules } from "@google/jules-sdk";
|
|
24
|
+
async function getSessionChangedFiles(client, sessionId) {
|
|
25
|
+
const session = client.session(sessionId);
|
|
26
|
+
await session.activities.hydrate();
|
|
27
|
+
const snapshot = await session.snapshot();
|
|
28
|
+
const isBusy = isBusyState(snapshot.state);
|
|
29
|
+
if (isBusy) {
|
|
30
|
+
return aggregateFromActivities(snapshot.activities ?? []);
|
|
31
|
+
}
|
|
32
|
+
const changeSet = typeof snapshot.changeSet === "function" ? snapshot.changeSet() : undefined;
|
|
33
|
+
if (!changeSet)
|
|
34
|
+
return [];
|
|
35
|
+
const parsed = changeSet.parsed();
|
|
36
|
+
return parsed.files.map((f) => ({
|
|
37
|
+
path: f.path,
|
|
38
|
+
changeType: f.changeType
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
function isBusyState(state) {
|
|
42
|
+
const busy = new Set([
|
|
43
|
+
"queued",
|
|
44
|
+
"planning",
|
|
45
|
+
"inProgress",
|
|
46
|
+
"in_progress"
|
|
47
|
+
]);
|
|
48
|
+
return busy.has(state);
|
|
49
|
+
}
|
|
50
|
+
function aggregateFromActivities(activities) {
|
|
51
|
+
const fileMap = new Map;
|
|
52
|
+
for (const activity of activities) {
|
|
53
|
+
for (const artifact of activity.artifacts) {
|
|
54
|
+
if (artifact.type === "changeSet") {
|
|
55
|
+
const parsed = artifact.parsed();
|
|
56
|
+
for (const file of parsed.files) {
|
|
57
|
+
const existing = fileMap.get(file.path);
|
|
58
|
+
if (existing) {
|
|
59
|
+
existing.latestChangeType = file.changeType;
|
|
60
|
+
} else {
|
|
61
|
+
fileMap.set(file.path, {
|
|
62
|
+
firstChangeType: file.changeType,
|
|
63
|
+
latestChangeType: file.changeType
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const result = [];
|
|
71
|
+
for (const [path, info] of fileMap) {
|
|
72
|
+
if (info.firstChangeType === "created" && info.latestChangeType === "deleted") {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const netType = info.firstChangeType === "created" ? "created" : info.latestChangeType;
|
|
76
|
+
result.push({ path, changeType: netType });
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/shared/github.ts
|
|
82
|
+
import { Octokit } from "@octokit/rest";
|
|
83
|
+
import { createAppAuth } from "@octokit/auth-app";
|
|
84
|
+
async function compareCommits(octokit, owner, repo, base, head) {
|
|
85
|
+
const { data } = await octokit.repos.compareCommits({
|
|
86
|
+
owner,
|
|
87
|
+
repo,
|
|
88
|
+
base,
|
|
89
|
+
head
|
|
90
|
+
});
|
|
91
|
+
return (data.files ?? []).map((f) => f.filename);
|
|
92
|
+
}
|
|
93
|
+
async function getFileContent(octokit, owner, repo, path, ref) {
|
|
94
|
+
try {
|
|
95
|
+
const { data } = await octokit.repos.getContent({
|
|
96
|
+
owner,
|
|
97
|
+
repo,
|
|
98
|
+
path,
|
|
99
|
+
ref
|
|
100
|
+
});
|
|
101
|
+
if ("content" in data && typeof data.content === "string") {
|
|
102
|
+
return Buffer.from(data.content, "base64").toString("utf-8");
|
|
103
|
+
}
|
|
104
|
+
return "";
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error?.status === 404) {
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/conflicts/session-handler.ts
|
|
114
|
+
class SessionCheckHandler {
|
|
115
|
+
octokit;
|
|
116
|
+
julesClient;
|
|
117
|
+
constructor(octokit, julesClient) {
|
|
118
|
+
this.octokit = octokit;
|
|
119
|
+
this.julesClient = julesClient;
|
|
120
|
+
}
|
|
121
|
+
async execute(input) {
|
|
122
|
+
try {
|
|
123
|
+
const [owner, repo] = input.repo.split("/");
|
|
124
|
+
let sessionPaths;
|
|
125
|
+
try {
|
|
126
|
+
const sessionFiles = await getSessionChangedFiles(this.julesClient, input.sessionId);
|
|
127
|
+
sessionPaths = sessionFiles.map((f) => f.path);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return fail("SESSION_QUERY_FAILED", `Failed to query session ${input.sessionId}: ${error.message}`, true, "Verify the session ID is correct and JULES_API_KEY is set.");
|
|
130
|
+
}
|
|
131
|
+
let remoteFiles;
|
|
132
|
+
try {
|
|
133
|
+
remoteFiles = await compareCommits(this.octokit, owner, repo, input.base, "HEAD");
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return fail("GITHUB_API_ERROR", `Failed to compare commits: ${error.message}`, true, "Check GITHUB_TOKEN and repository access.");
|
|
136
|
+
}
|
|
137
|
+
const remoteSet = new Set(remoteFiles);
|
|
138
|
+
const overlapping = sessionPaths.filter((f) => remoteSet.has(f));
|
|
139
|
+
if (overlapping.length === 0) {
|
|
140
|
+
return ok({
|
|
141
|
+
status: "clean",
|
|
142
|
+
message: "No conflicts detected.",
|
|
143
|
+
conflicts: []
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const conflicts = await Promise.all(overlapping.map(async (filePath) => {
|
|
147
|
+
const remoteShadowContent = await getFileContent(this.octokit, owner, repo, filePath, input.base);
|
|
148
|
+
return {
|
|
149
|
+
filePath,
|
|
150
|
+
conflictReason: "Remote commit modified this file since branch creation.",
|
|
151
|
+
remoteShadowContent
|
|
152
|
+
};
|
|
153
|
+
}));
|
|
154
|
+
return ok({
|
|
155
|
+
status: "conflict",
|
|
156
|
+
message: `The remote ${input.base} branch has advanced. Rebase required for ${overlapping.join(", ")}.`,
|
|
157
|
+
conflicts
|
|
158
|
+
});
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return fail("UNKNOWN_ERROR", error instanceof Error ? error.message : String(error), false);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// src/conflicts/git-spec.ts
|
|
165
|
+
import { z as z2 } from "zod";
|
|
166
|
+
var GitCheckInputSchema = z2.object({
|
|
167
|
+
repo: z2.string().min(1).refine((s) => s.includes("/"), "Must be in owner/repo format"),
|
|
168
|
+
pullRequestNumber: z2.number().int().positive(),
|
|
169
|
+
failingCommitSha: z2.string().min(1)
|
|
170
|
+
});
|
|
171
|
+
var GitCheckErrorCode = z2.enum([
|
|
172
|
+
"NO_UNMERGED_FILES",
|
|
173
|
+
"GIT_STATUS_FAILED",
|
|
174
|
+
"FILE_READ_FAILED",
|
|
175
|
+
"UNKNOWN_ERROR"
|
|
176
|
+
]);
|
|
177
|
+
// src/conflicts/git-handler.ts
|
|
178
|
+
import { readFile } from "node:fs/promises";
|
|
179
|
+
|
|
180
|
+
// src/shared/git.ts
|
|
181
|
+
import { execFile } from "node:child_process";
|
|
182
|
+
import { promisify } from "node:util";
|
|
183
|
+
var execFileAsync = promisify(execFile);
|
|
184
|
+
async function gitStatusUnmerged(cwd) {
|
|
185
|
+
try {
|
|
186
|
+
const { stdout } = await execFileAsync("git", ["status", "--porcelain"], { cwd });
|
|
187
|
+
const files = stdout.split(`
|
|
188
|
+
`).filter((line) => line.startsWith("UU ")).map((line) => line.slice(3).trim());
|
|
189
|
+
return { ok: true, data: files };
|
|
190
|
+
} catch (error) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
error: error instanceof Error ? error.message : String(error)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function gitMergeBase(head, base, cwd) {
|
|
198
|
+
try {
|
|
199
|
+
const { stdout } = await execFileAsync("git", ["merge-base", head, base], { cwd });
|
|
200
|
+
return { ok: true, data: stdout.trim() };
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return {
|
|
203
|
+
ok: false,
|
|
204
|
+
error: error instanceof Error ? error.message : String(error)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/conflicts/git-handler.ts
|
|
210
|
+
class GitCheckHandler {
|
|
211
|
+
async execute(input) {
|
|
212
|
+
try {
|
|
213
|
+
const statusResult = await gitStatusUnmerged();
|
|
214
|
+
if (!statusResult.ok) {
|
|
215
|
+
return fail("GIT_STATUS_FAILED", `Failed to get git status: ${statusResult.error}`, true, "Ensure git is available and the working directory is a repository.");
|
|
216
|
+
}
|
|
217
|
+
const unmergedFiles = statusResult.data;
|
|
218
|
+
if (unmergedFiles.length === 0) {
|
|
219
|
+
return fail("NO_UNMERGED_FILES", "No unmerged files found. The merge conflict may have already been resolved.", false);
|
|
220
|
+
}
|
|
221
|
+
const affectedFiles = await Promise.all(unmergedFiles.map(async (filePath) => {
|
|
222
|
+
let content;
|
|
223
|
+
try {
|
|
224
|
+
content = await readFile(filePath, "utf-8");
|
|
225
|
+
} catch (error) {
|
|
226
|
+
return {
|
|
227
|
+
filePath,
|
|
228
|
+
baseCommitSha: "",
|
|
229
|
+
gitConflictMarkers: `[Error reading file: ${error.message}]`
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const markers = extractConflictMarkers(content);
|
|
233
|
+
return {
|
|
234
|
+
filePath,
|
|
235
|
+
baseCommitSha: "",
|
|
236
|
+
gitConflictMarkers: markers
|
|
237
|
+
};
|
|
238
|
+
}));
|
|
239
|
+
const mergeBaseResult = await gitMergeBase(input.failingCommitSha, "HEAD");
|
|
240
|
+
const baseSha = mergeBaseResult.ok ? mergeBaseResult.data : "unknown";
|
|
241
|
+
for (const file of affectedFiles) {
|
|
242
|
+
file.baseCommitSha = baseSha;
|
|
243
|
+
}
|
|
244
|
+
const taskDirective = [
|
|
245
|
+
`MERGE CONFLICT RESOLUTION REQUIRED for PR #${input.pullRequestNumber}.`,
|
|
246
|
+
`Failing commit: ${input.failingCommitSha}.`,
|
|
247
|
+
`${affectedFiles.length} file(s) have unresolved conflicts.`,
|
|
248
|
+
`Review the gitConflictMarkers for each file and rewrite the code to resolve all conflicts.`
|
|
249
|
+
].join(`
|
|
250
|
+
`);
|
|
251
|
+
return ok({
|
|
252
|
+
taskDirective,
|
|
253
|
+
priority: "critical",
|
|
254
|
+
affectedFiles
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return fail("UNKNOWN_ERROR", error instanceof Error ? error.message : String(error), false);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function extractConflictMarkers(content) {
|
|
262
|
+
const lines = content.split(`
|
|
263
|
+
`);
|
|
264
|
+
const blocks = [];
|
|
265
|
+
let inConflict = false;
|
|
266
|
+
let currentBlock = [];
|
|
267
|
+
for (const line of lines) {
|
|
268
|
+
if (line.startsWith("<<<<<<<")) {
|
|
269
|
+
inConflict = true;
|
|
270
|
+
currentBlock = [line];
|
|
271
|
+
} else if (line.startsWith(">>>>>>>") && inConflict) {
|
|
272
|
+
currentBlock.push(line);
|
|
273
|
+
blocks.push(currentBlock.join(`
|
|
274
|
+
`));
|
|
275
|
+
inConflict = false;
|
|
276
|
+
currentBlock = [];
|
|
277
|
+
} else if (inConflict) {
|
|
278
|
+
currentBlock.push(line);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return blocks.join(`
|
|
282
|
+
---
|
|
283
|
+
`);
|
|
284
|
+
}
|
|
285
|
+
// src/init/init-spec.ts
|
|
286
|
+
import { z as z3 } from "zod";
|
|
287
|
+
var InitInputSchema = z3.object({
|
|
288
|
+
outputDir: z3.string().min(1).default("."),
|
|
289
|
+
workflowName: z3.string().min(1).default("jules-merge-check"),
|
|
290
|
+
baseBranch: z3.string().min(1).default("main"),
|
|
291
|
+
force: z3.boolean().default(false)
|
|
292
|
+
});
|
|
293
|
+
var InitErrorCode = z3.enum([
|
|
294
|
+
"DIRECTORY_NOT_FOUND",
|
|
295
|
+
"FILE_ALREADY_EXISTS",
|
|
296
|
+
"WRITE_FAILED",
|
|
297
|
+
"UNKNOWN_ERROR"
|
|
298
|
+
]);
|
|
299
|
+
// src/init/init-handler.ts
|
|
300
|
+
import { mkdir, writeFile, access, stat } from "node:fs/promises";
|
|
301
|
+
import { join, resolve } from "node:path";
|
|
302
|
+
|
|
303
|
+
// src/init/templates.ts
|
|
304
|
+
function buildWorkflowYaml(options) {
|
|
305
|
+
return `# Generated by jules-merge init
|
|
306
|
+
# This workflow checks for merge conflicts on pull requests.
|
|
307
|
+
name: ${options.workflowName}
|
|
308
|
+
|
|
309
|
+
on:
|
|
310
|
+
pull_request:
|
|
311
|
+
branches: [${options.baseBranch}]
|
|
312
|
+
|
|
313
|
+
permissions:
|
|
314
|
+
contents: read
|
|
315
|
+
pull-requests: read
|
|
316
|
+
|
|
317
|
+
jobs:
|
|
318
|
+
check-conflicts:
|
|
319
|
+
runs-on: ubuntu-latest
|
|
320
|
+
steps:
|
|
321
|
+
- uses: actions/checkout@v4
|
|
322
|
+
with:
|
|
323
|
+
fetch-depth: 0
|
|
324
|
+
|
|
325
|
+
- name: Attempt merge
|
|
326
|
+
id: merge
|
|
327
|
+
continue-on-error: true
|
|
328
|
+
run: |
|
|
329
|
+
git config user.name "github-actions"
|
|
330
|
+
git config user.email "github-actions@github.com"
|
|
331
|
+
git fetch origin \${{ github.event.pull_request.base.ref }}
|
|
332
|
+
git merge origin/\${{ github.event.pull_request.base.ref }} --no-commit --no-ff || true
|
|
333
|
+
|
|
334
|
+
- name: Check for conflicts
|
|
335
|
+
run: |
|
|
336
|
+
npx @google/jules-merge check-conflicts \\
|
|
337
|
+
--repo \${{ github.repository }} \\
|
|
338
|
+
--pr \${{ github.event.pull_request.number }} \\
|
|
339
|
+
--sha \${{ github.event.pull_request.head.sha }}
|
|
340
|
+
`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/init/init-handler.ts
|
|
344
|
+
class InitHandler {
|
|
345
|
+
async execute(input) {
|
|
346
|
+
try {
|
|
347
|
+
const outputDir = resolve(input.outputDir);
|
|
348
|
+
try {
|
|
349
|
+
const stats = await stat(outputDir);
|
|
350
|
+
if (!stats.isDirectory()) {
|
|
351
|
+
return fail("DIRECTORY_NOT_FOUND", `${outputDir} is not a directory.`, true, "Provide a valid directory path with --output-dir.");
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
return fail("DIRECTORY_NOT_FOUND", `Directory does not exist: ${outputDir}`, true, "Create the directory first or provide a valid path.");
|
|
355
|
+
}
|
|
356
|
+
const workflowDir = join(outputDir, ".github", "workflows");
|
|
357
|
+
const filePath = join(workflowDir, `${input.workflowName}.yml`);
|
|
358
|
+
if (!input.force) {
|
|
359
|
+
try {
|
|
360
|
+
await access(filePath);
|
|
361
|
+
return fail("FILE_ALREADY_EXISTS", `Workflow file already exists: ${filePath}`, true, "Use --force to overwrite.");
|
|
362
|
+
} catch {}
|
|
363
|
+
}
|
|
364
|
+
const content = buildWorkflowYaml({
|
|
365
|
+
workflowName: input.workflowName,
|
|
366
|
+
baseBranch: input.baseBranch
|
|
367
|
+
});
|
|
368
|
+
try {
|
|
369
|
+
await mkdir(workflowDir, { recursive: true });
|
|
370
|
+
await writeFile(filePath, content, "utf-8");
|
|
371
|
+
} catch (error) {
|
|
372
|
+
return fail("WRITE_FAILED", `Failed to write workflow file: ${error.message}`, true);
|
|
373
|
+
}
|
|
374
|
+
return ok({ filePath, content });
|
|
375
|
+
} catch (error) {
|
|
376
|
+
return fail("UNKNOWN_ERROR", error instanceof Error ? error.message : String(error), false);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
export {
|
|
381
|
+
ok,
|
|
382
|
+
getSessionChangedFiles,
|
|
383
|
+
fail,
|
|
384
|
+
jules as createJulesClient,
|
|
385
|
+
buildWorkflowYaml,
|
|
386
|
+
SessionCheckInputSchema,
|
|
387
|
+
SessionCheckHandler,
|
|
388
|
+
SessionCheckErrorCode,
|
|
389
|
+
InitInputSchema,
|
|
390
|
+
InitHandler,
|
|
391
|
+
InitErrorCode,
|
|
392
|
+
GitCheckInputSchema,
|
|
393
|
+
GitCheckHandler,
|
|
394
|
+
GitCheckErrorCode
|
|
395
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare const _default: import("citty").CommandDef<{
|
|
2
|
+
session: {
|
|
3
|
+
type: "string";
|
|
4
|
+
description: string;
|
|
5
|
+
};
|
|
6
|
+
repo: {
|
|
7
|
+
type: "string";
|
|
8
|
+
description: string;
|
|
9
|
+
};
|
|
10
|
+
base: {
|
|
11
|
+
type: "string";
|
|
12
|
+
description: string;
|
|
13
|
+
default: string;
|
|
14
|
+
};
|
|
15
|
+
pr: {
|
|
16
|
+
type: "string";
|
|
17
|
+
description: string;
|
|
18
|
+
};
|
|
19
|
+
sha: {
|
|
20
|
+
type: "string";
|
|
21
|
+
description: string;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
export default _default;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare const _default: import("citty").CommandDef<{
|
|
2
|
+
'output-dir': {
|
|
3
|
+
type: "string";
|
|
4
|
+
description: string;
|
|
5
|
+
default: string;
|
|
6
|
+
};
|
|
7
|
+
'workflow-name': {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
default: string;
|
|
11
|
+
};
|
|
12
|
+
'base-branch': {
|
|
13
|
+
type: "string";
|
|
14
|
+
description: string;
|
|
15
|
+
default: string;
|
|
16
|
+
};
|
|
17
|
+
force: {
|
|
18
|
+
type: "boolean";
|
|
19
|
+
description: string;
|
|
20
|
+
default: false;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const GitCheckInputSchema: z.ZodObject<{
|
|
3
|
+
repo: z.ZodEffects<z.ZodString, string, string>;
|
|
4
|
+
pullRequestNumber: z.ZodNumber;
|
|
5
|
+
failingCommitSha: z.ZodString;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
repo: string;
|
|
8
|
+
pullRequestNumber: number;
|
|
9
|
+
failingCommitSha: string;
|
|
10
|
+
}, {
|
|
11
|
+
repo: string;
|
|
12
|
+
pullRequestNumber: number;
|
|
13
|
+
failingCommitSha: string;
|
|
14
|
+
}>;
|
|
15
|
+
export type GitCheckInput = z.infer<typeof GitCheckInputSchema>;
|
|
16
|
+
export declare const GitCheckErrorCode: z.ZodEnum<["NO_UNMERGED_FILES", "GIT_STATUS_FAILED", "FILE_READ_FAILED", "UNKNOWN_ERROR"]>;
|
|
17
|
+
export type GitCheckErrorCode = z.infer<typeof GitCheckErrorCode>;
|
|
18
|
+
export interface GitCheckData {
|
|
19
|
+
taskDirective: string;
|
|
20
|
+
priority: 'standard' | 'critical';
|
|
21
|
+
affectedFiles: Array<{
|
|
22
|
+
filePath: string;
|
|
23
|
+
baseCommitSha: string;
|
|
24
|
+
gitConflictMarkers: string;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export interface GitCheckSuccess {
|
|
28
|
+
success: true;
|
|
29
|
+
data: GitCheckData;
|
|
30
|
+
}
|
|
31
|
+
export interface GitCheckFailure {
|
|
32
|
+
success: false;
|
|
33
|
+
error: {
|
|
34
|
+
code: GitCheckErrorCode;
|
|
35
|
+
message: string;
|
|
36
|
+
recoverable: boolean;
|
|
37
|
+
suggestion?: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export type GitCheckResult = GitCheckSuccess | GitCheckFailure;
|
|
41
|
+
export interface GitCheckSpec {
|
|
42
|
+
execute(input: GitCheckInput): Promise<GitCheckResult>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Octokit } from '@octokit/rest';
|
|
2
|
+
import type { JulesClient } from '@google/jules-sdk';
|
|
3
|
+
import type { SessionCheckSpec, SessionCheckInput, SessionCheckResult } from './session-spec.js';
|
|
4
|
+
export declare class SessionCheckHandler implements SessionCheckSpec {
|
|
5
|
+
private octokit;
|
|
6
|
+
private julesClient;
|
|
7
|
+
constructor(octokit: Octokit, julesClient: JulesClient);
|
|
8
|
+
execute(input: SessionCheckInput): Promise<SessionCheckResult>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const SessionCheckInputSchema: z.ZodObject<{
|
|
3
|
+
sessionId: z.ZodString;
|
|
4
|
+
repo: z.ZodEffects<z.ZodString, string, string>;
|
|
5
|
+
base: z.ZodDefault<z.ZodString>;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
repo: string;
|
|
9
|
+
base: string;
|
|
10
|
+
}, {
|
|
11
|
+
sessionId: string;
|
|
12
|
+
repo: string;
|
|
13
|
+
base?: string | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
export type SessionCheckInput = z.infer<typeof SessionCheckInputSchema>;
|
|
16
|
+
export declare const SessionCheckErrorCode: z.ZodEnum<["SESSION_QUERY_FAILED", "GITHUB_API_ERROR", "RATE_LIMIT_EXCEEDED", "UNKNOWN_ERROR"]>;
|
|
17
|
+
export type SessionCheckErrorCode = z.infer<typeof SessionCheckErrorCode>;
|
|
18
|
+
export interface SessionCheckData {
|
|
19
|
+
status: 'clean' | 'conflict';
|
|
20
|
+
message: string;
|
|
21
|
+
conflicts: Array<{
|
|
22
|
+
filePath: string;
|
|
23
|
+
conflictReason: string;
|
|
24
|
+
remoteShadowContent: string;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export interface SessionCheckSuccess {
|
|
28
|
+
success: true;
|
|
29
|
+
data: SessionCheckData;
|
|
30
|
+
}
|
|
31
|
+
export interface SessionCheckFailure {
|
|
32
|
+
success: false;
|
|
33
|
+
error: {
|
|
34
|
+
code: SessionCheckErrorCode;
|
|
35
|
+
message: string;
|
|
36
|
+
recoverable: boolean;
|
|
37
|
+
suggestion?: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export type SessionCheckResult = SessionCheckSuccess | SessionCheckFailure;
|
|
41
|
+
export interface SessionCheckSpec {
|
|
42
|
+
execute(input: SessionCheckInput): Promise<SessionCheckResult>;
|
|
43
|
+
}
|