@elyracode/git-intel 0.5.2

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 ADDED
@@ -0,0 +1,36 @@
1
+ # @elyracode/git-intel
2
+
3
+ Git intelligence for Elyra -- session briefings, commit messages, and PR descriptions.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ elyra install npm:@elyracode/git-intel
9
+ ```
10
+
11
+ ## Tools
12
+
13
+ | Tool | Description |
14
+ |------|-------------|
15
+ | `git_briefing` | Summary of recent commits, changed files, branches, and working tree status |
16
+ | `generate_commit_message` | Analyze staged diff and suggest a conventional commit message |
17
+ | `generate_pr_description` | Analyze branch diff and generate a PR description with what/why/test/checklist |
18
+
19
+ ## Commands
20
+
21
+ | Command | Description |
22
+ |---------|-------------|
23
+ | `/briefing` | What changed since your last session? |
24
+ | `/commit` | Generate a commit message from staged changes |
25
+ | `/pr` | Generate a PR description for the current branch |
26
+
27
+ ## Usage
28
+
29
+ ```
30
+ > What changed in this repo since yesterday?
31
+ > Generate a commit message for my staged changes
32
+ > Write a PR description for this branch
33
+ /briefing
34
+ /commit
35
+ /pr
36
+ ```
@@ -0,0 +1,284 @@
1
+ import { execSync } from "node:child_process";
2
+ import type { ExtensionAPI } from "@elyracode/coding-agent";
3
+ import { Type } from "typebox";
4
+
5
+ export default function (elyra: ExtensionAPI): void {
6
+
7
+ // ── Tool: git_briefing ──
8
+ elyra.registerTool({
9
+ name: "git_briefing",
10
+ label: "Git Briefing",
11
+ description:
12
+ "Get a summary of what happened in the git repository since a given time or number of commits. " +
13
+ "Shows: recent commits by others, files you worked on that changed, branches, and potential conflicts. " +
14
+ "Use this at the start of a session to understand what changed.",
15
+ parameters: Type.Object({
16
+ since: Type.Optional(
17
+ Type.String({ description: "Time period (e.g., '24 hours ago', 'yesterday', '3 days ago'). Default: '24 hours ago'" }),
18
+ ),
19
+ }),
20
+ execute: async (_toolCallId, params) => {
21
+ try {
22
+ const cwd = process.cwd();
23
+ const since = params.since ?? "24 hours ago";
24
+
25
+ const lines: string[] = ["# Git Briefing", ""];
26
+
27
+ // Current branch
28
+ const branch = git(cwd, "rev-parse --abbrev-ref HEAD");
29
+ lines.push(`**Branch**: ${branch}`);
30
+
31
+ // Recent commits
32
+ const commits = git(cwd, `log --oneline --since="${since}" --no-merges`);
33
+ const commitLines = commits.split("\n").filter((l) => l.trim());
34
+ lines.push(`**Commits since ${since}**: ${commitLines.length}`, "");
35
+
36
+ if (commitLines.length > 0) {
37
+ lines.push("## Recent Commits");
38
+ for (const line of commitLines.slice(0, 20)) {
39
+ lines.push(`- ${line}`);
40
+ }
41
+ if (commitLines.length > 20) {
42
+ lines.push(`- ... and ${commitLines.length - 20} more`);
43
+ }
44
+ lines.push("");
45
+ }
46
+
47
+ // Authors
48
+ const authors = git(cwd, `log --format="%an" --since="${since}" --no-merges`);
49
+ const authorSet = new Set(authors.split("\n").filter((l) => l.trim()));
50
+ if (authorSet.size > 0) {
51
+ lines.push(`**Contributors**: ${[...authorSet].join(", ")}`, "");
52
+ }
53
+
54
+ // Changed files
55
+ const changedFiles = git(cwd, `diff --name-only HEAD~${Math.min(commitLines.length, 50)}..HEAD 2>/dev/null || echo ""`);
56
+ const fileList = changedFiles.split("\n").filter((l) => l.trim());
57
+ if (fileList.length > 0) {
58
+ lines.push(`## Changed Files (${fileList.length})`);
59
+ for (const file of fileList.slice(0, 30)) {
60
+ lines.push(`- ${file}`);
61
+ }
62
+ if (fileList.length > 30) {
63
+ lines.push(`- ... and ${fileList.length - 30} more`);
64
+ }
65
+ lines.push("");
66
+ }
67
+
68
+ // Uncommitted changes
69
+ const status = git(cwd, "status --porcelain");
70
+ const statusLines = status.split("\n").filter((l) => l.trim());
71
+ if (statusLines.length > 0) {
72
+ const modified = statusLines.filter((l) => !l.startsWith("??")).length;
73
+ const untracked = statusLines.filter((l) => l.startsWith("??")).length;
74
+ lines.push("## Working Tree");
75
+ if (modified > 0) lines.push(`- ${modified} modified/staged file${modified > 1 ? "s" : ""}`);
76
+ if (untracked > 0) lines.push(`- ${untracked} untracked file${untracked > 1 ? "s" : ""}`);
77
+ lines.push("");
78
+ }
79
+
80
+ // Branches with recent activity
81
+ const branches = git(cwd, "branch -a --sort=-committerdate --format='%(refname:short) %(committerdate:relative)'");
82
+ const branchLines = branches.split("\n").filter((l) => l.trim()).slice(0, 10);
83
+ if (branchLines.length > 1) {
84
+ lines.push("## Active Branches");
85
+ for (const b of branchLines) {
86
+ lines.push(`- ${b}`);
87
+ }
88
+ lines.push("");
89
+ }
90
+
91
+ return {
92
+ content: [{ type: "text", text: lines.join("\n") }],
93
+ details: { commits: commitLines.length, files: fileList.length },
94
+ };
95
+ } catch (error) {
96
+ const msg = error instanceof Error ? error.message : String(error);
97
+ return {
98
+ content: [{ type: "text", text: `Git briefing failed: ${msg}` }],
99
+ details: {},
100
+ };
101
+ }
102
+ },
103
+ });
104
+
105
+ // ── Tool: generate_commit_message ──
106
+ elyra.registerTool({
107
+ name: "generate_commit_message",
108
+ label: "Generate Commit Message",
109
+ description:
110
+ "Analyze staged git changes and generate a conventional commit message. " +
111
+ "Returns the diff summary and a suggested commit message following the format: " +
112
+ "type(scope): description. Use this when the user wants to commit changes.",
113
+ parameters: Type.Object({
114
+ include_body: Type.Optional(
115
+ Type.Boolean({ description: "Include detailed body in the commit message (default: false)" }),
116
+ ),
117
+ }),
118
+ execute: async (_toolCallId, params) => {
119
+ try {
120
+ const cwd = process.cwd();
121
+
122
+ // Get staged diff
123
+ let diff = git(cwd, "diff --cached --stat");
124
+ if (!diff.trim()) {
125
+ // Nothing staged, show unstaged
126
+ diff = git(cwd, "diff --stat");
127
+ if (!diff.trim()) {
128
+ return {
129
+ content: [{ type: "text", text: "No changes to commit (nothing staged or modified)." }],
130
+ details: {},
131
+ };
132
+ }
133
+ return {
134
+ content: [{
135
+ type: "text",
136
+ text: `No staged changes. Stage files first with \`git add\`.\n\nUnstaged changes:\n${diff}`,
137
+ }],
138
+ details: {},
139
+ };
140
+ }
141
+
142
+ const diffContent = git(cwd, "diff --cached");
143
+ const truncatedDiff = diffContent.length > 30000
144
+ ? `${diffContent.slice(0, 30000)}\n\n... (truncated)`
145
+ : diffContent;
146
+
147
+ const lines: string[] = [
148
+ "# Staged Changes",
149
+ "",
150
+ "## Summary",
151
+ diff,
152
+ "",
153
+ "## Diff",
154
+ "```",
155
+ truncatedDiff,
156
+ "```",
157
+ "",
158
+ "Generate a conventional commit message for these changes.",
159
+ "Format: type(scope): short description",
160
+ "",
161
+ "Types: feat, fix, chore, docs, refactor, test, style, perf",
162
+ "Scope: the package or area affected",
163
+ params.include_body
164
+ ? "Include a body explaining what and why (not how)."
165
+ : "Keep it to a single line.",
166
+ ];
167
+
168
+ return {
169
+ content: [{ type: "text", text: lines.join("\n") }],
170
+ details: {},
171
+ };
172
+ } catch (error) {
173
+ const msg = error instanceof Error ? error.message : String(error);
174
+ return {
175
+ content: [{ type: "text", text: `Commit message generation failed: ${msg}` }],
176
+ details: {},
177
+ };
178
+ }
179
+ },
180
+ });
181
+
182
+ // ── Tool: generate_pr_description ──
183
+ elyra.registerTool({
184
+ name: "generate_pr_description",
185
+ label: "Generate PR Description",
186
+ description:
187
+ "Analyze the current branch diff against main/master and generate a PR description. " +
188
+ "Includes: what changed, why, how to test, and a checklist. " +
189
+ "Use this before creating a pull request.",
190
+ parameters: Type.Object({
191
+ base: Type.Optional(
192
+ Type.String({ description: "Base branch to compare against (default: auto-detect main or master)" }),
193
+ ),
194
+ }),
195
+ execute: async (_toolCallId, params) => {
196
+ try {
197
+ const cwd = process.cwd();
198
+
199
+ // Detect base branch
200
+ let base = params.base;
201
+ if (!base) {
202
+ try {
203
+ git(cwd, "rev-parse --verify main");
204
+ base = "main";
205
+ } catch {
206
+ base = "master";
207
+ }
208
+ }
209
+
210
+ const currentBranch = git(cwd, "rev-parse --abbrev-ref HEAD");
211
+ const commits = git(cwd, `log --oneline ${base}..HEAD`);
212
+ const diffStat = git(cwd, `diff --stat ${base}..HEAD`);
213
+ const diff = git(cwd, `diff ${base}..HEAD`);
214
+
215
+ const truncatedDiff = diff.length > 50000
216
+ ? `${diff.slice(0, 50000)}\n\n... (truncated)`
217
+ : diff;
218
+
219
+ const lines: string[] = [
220
+ `# PR: ${currentBranch} -> ${base}`,
221
+ "",
222
+ "## Commits",
223
+ commits,
224
+ "",
225
+ "## Files Changed",
226
+ diffStat,
227
+ "",
228
+ "## Diff",
229
+ "```",
230
+ truncatedDiff,
231
+ "```",
232
+ "",
233
+ "Generate a PR description with:",
234
+ "- **What**: summary of changes",
235
+ "- **Why**: motivation and context",
236
+ "- **How to test**: verification steps",
237
+ "- **Checklist**: [ ] tests, [ ] docs, [ ] breaking changes",
238
+ ];
239
+
240
+ return {
241
+ content: [{ type: "text", text: lines.join("\n") }],
242
+ details: { branch: currentBranch, base, commitCount: commits.split("\n").filter((l) => l.trim()).length },
243
+ };
244
+ } catch (error) {
245
+ const msg = error instanceof Error ? error.message : String(error);
246
+ return {
247
+ content: [{ type: "text", text: `PR description generation failed: ${msg}` }],
248
+ details: {},
249
+ };
250
+ }
251
+ },
252
+ });
253
+
254
+ // ── Commands ──
255
+ elyra.registerCommand("briefing", {
256
+ description: "Get a git briefing -- what changed since your last session",
257
+ handler: async (_args, _ctx) => {
258
+ elyra.sendUserMessage("Give me a git briefing. What changed in this repository recently?");
259
+ },
260
+ });
261
+
262
+ elyra.registerCommand("commit", {
263
+ description: "Generate a commit message from staged changes",
264
+ handler: async (_args, _ctx) => {
265
+ elyra.sendUserMessage("Generate a conventional commit message for my staged changes.");
266
+ },
267
+ });
268
+
269
+ elyra.registerCommand("pr", {
270
+ description: "Generate a PR description from the current branch",
271
+ handler: async (_args, _ctx) => {
272
+ elyra.sendUserMessage("Generate a PR description for the current branch.");
273
+ },
274
+ });
275
+ }
276
+
277
+ function git(cwd: string, command: string): string {
278
+ return execSync(`git --no-pager ${command}`, {
279
+ cwd,
280
+ timeout: 15000,
281
+ encoding: "utf-8",
282
+ stdio: ["pipe", "pipe", "pipe"],
283
+ }).trim();
284
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@elyracode/git-intel",
3
+ "version": "0.5.2",
4
+ "description": "Elyra extension for Git intelligence -- session briefings, commit message generation, PR descriptions",
5
+ "type": "module",
6
+ "keywords": ["elyra-package", "git", "commit", "pr", "diff", "intelligence"],
7
+ "license": "MIT",
8
+ "author": "Knut W. Horne",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/kwhorne/elyra.git",
12
+ "directory": "packages/git-intel"
13
+ },
14
+ "elyra": {
15
+ "extensions": ["./extensions/index.ts"]
16
+ },
17
+ "peerDependencies": {
18
+ "@elyracode/coding-agent": "*",
19
+ "typebox": "*"
20
+ },
21
+ "scripts": {
22
+ "clean": "echo 'nothing to clean'",
23
+ "build": "echo 'nothing to build'",
24
+ "check": "echo 'nothing to check'"
25
+ }
26
+ }