sf-git-ai-meta-insights 1.0.0
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/CHANGELOG.md +17 -0
- package/LICENSE.md +9 -0
- package/README.md +109 -0
- package/lib/ai/metadataSummary.d.ts +19 -0
- package/lib/ai/metadataSummary.js +129 -0
- package/lib/ai/metadataSummary.js.map +1 -0
- package/lib/ai/openAiConfig.d.ts +38 -0
- package/lib/ai/openAiConfig.js +112 -0
- package/lib/ai/openAiConfig.js.map +1 -0
- package/lib/commands/sgai/metadata/summarize.d.ts +20 -0
- package/lib/commands/sgai/metadata/summarize.js +110 -0
- package/lib/commands/sgai/metadata/summarize.js.map +1 -0
- package/lib/git/gitDiff.d.ts +11 -0
- package/lib/git/gitDiff.js +105 -0
- package/lib/git/gitDiff.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/messages/sgai.metadata.summarize.md +77 -0
- package/oclif.lock +9813 -0
- package/oclif.manifest.json +137 -0
- package/package.json +209 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 (2026-04-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* init release ([3ef828a](https://github.com/mcarvin8/sf-git-ai-meta-insights/commit/3ef828abaca2649cf726c9295f8bb3095743fc2c))
|
|
9
|
+
* **sgai:** llm gateway auth, diff size limits, and coverage ([4e5d5dc](https://github.com/mcarvin8/sf-git-ai-meta-insights/commit/4e5d5dc7ffcf7fc53ed0af2218d1e87c2ffcb195))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **deps:** bump @salesforce/sf-plugins-core from 12.0.11 to 12.2.6 ([#3](https://github.com/mcarvin8/sf-git-ai-meta-insights/issues/3)) ([2222526](https://github.com/mcarvin8/sf-git-ai-meta-insights/commit/22225267752a399095ae43e69f3f8f904381f084))
|
|
15
|
+
* **deps:** bump openai from 5.23.2 to 6.34.0 ([#7](https://github.com/mcarvin8/sf-git-ai-meta-insights/issues/7)) ([0a03423](https://github.com/mcarvin8/sf-git-ai-meta-insights/commit/0a034230ee192fabc6e99e55d1a2a1560d4cbf43))
|
|
16
|
+
|
|
17
|
+
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Carvin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# sf-git-ai-meta-insights
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/sf-git-ai-meta-insights) [](https://npmjs.org/package/sf-git-ai-meta-insights) [](https://raw.githubusercontent.com/salesforcecli/sf-git-ai-meta-insights/main/LICENSE.md)
|
|
4
|
+
|
|
5
|
+
`sf-git-ai-meta-insights` is an `sf` plugin that generates AI-assisted summaries of Salesforce metadata changes from a git diff.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This plugin summarizes metadata changes between two Git refs, optionally filters commits by message, and writes a Markdown summary file. When an **OpenAI-compatible** LLM gateway is configured (see below), it calls that HTTP API; otherwise it falls back to a local summary.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
sf plugins install sf-git-ai-meta-insights@latest
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Command
|
|
18
|
+
|
|
19
|
+
### `sf sgai metadata summarize`
|
|
20
|
+
|
|
21
|
+
Summarize metadata changes between two Git refs and write the generated summary to a Markdown file.
|
|
22
|
+
|
|
23
|
+
#### Flags
|
|
24
|
+
|
|
25
|
+
- `--from` `-f` (**required**) Start reference for the git diff range. You must pass an explicit ref (for example `HEAD~1`, a tag, or a commit hash); there is no default.
|
|
26
|
+
- `--to` `-t` End reference for the git diff range. Defaults to `HEAD`.
|
|
27
|
+
- `--message-filter` `-m` Regex filter for commit messages.
|
|
28
|
+
- `--team` Optional team or squad label for the summary (also supported via `METADATA_AUDIT_TEAM` or `SF_GIT_AI_TEAM`).
|
|
29
|
+
- `--output` `-p` Output file path for the generated summary. Defaults to `metadata-summary.md`.
|
|
30
|
+
- `--model` OpenAI model used for the summary. Defaults to `gpt-4o-mini`.
|
|
31
|
+
- `--ignore-package-directory` `-i` Ignore package directories defined in `sfdx-project.json`. This flag may be provided multiple times.
|
|
32
|
+
|
|
33
|
+
#### Examples
|
|
34
|
+
|
|
35
|
+
Summarize changes since the previous commit:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
sf sgai metadata summarize --from HEAD~1 --to HEAD
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Summarize a custom range and save to `changes.md`:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
sf sgai metadata summarize --from HEAD~5 --to HEAD --output changes.md
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Summarize only commits whose messages match a regex:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
sf sgai metadata summarize --from main --to HEAD --message-filter "(feature|fix)"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Use a custom OpenAI model:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
sf sgai metadata summarize --from HEAD~1 --to HEAD --model gpt-4o-mini
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Requirements
|
|
60
|
+
|
|
61
|
+
- `sf` CLI installed
|
|
62
|
+
- Node.js 20 or later
|
|
63
|
+
- A Salesforce DX project repository with a `sfdx-project.json` file present in the repo root
|
|
64
|
+
- Optional: LLM configuration (see below)
|
|
65
|
+
|
|
66
|
+
This plugin depends on running inside an SFDX project because it reads `packageDirectories` from `sfdx-project.json` to determine which metadata files from the git diff to include in the summary.
|
|
67
|
+
|
|
68
|
+
If no LLM gateway is configured, the plugin still generates a local fallback summary.
|
|
69
|
+
|
|
70
|
+
The plugin uses the official Node [`openai`](https://www.npmjs.com/package/openai) client: optional **`baseURL`**, optional **`defaultHeaders`** (JSON), and an API key string the SDK expects.
|
|
71
|
+
|
|
72
|
+
### `LLM_*` overrides `OPENAI_*`
|
|
73
|
+
|
|
74
|
+
| Variable | Purpose |
|
|
75
|
+
| ------------------------ | ------------------------------------------------------------------------------------------- |
|
|
76
|
+
| `OPENAI_API_KEY` | Default API key for api.openai.com or your gateway. |
|
|
77
|
+
| `LLM_API_KEY` | Overrides `OPENAI_API_KEY` when set. |
|
|
78
|
+
| `OPENAI_BASE_URL` | Default base URL (OpenAI-compatible). |
|
|
79
|
+
| `LLM_BASE_URL` | Overrides `OPENAI_BASE_URL` when set. |
|
|
80
|
+
| `OPENAI_DEFAULT_HEADERS` | JSON object of extra HTTP headers (string values only). |
|
|
81
|
+
| `LLM_DEFAULT_HEADERS` | JSON object merged **on top of** `OPENAI_DEFAULT_HEADERS` (same header names are replaced). |
|
|
82
|
+
|
|
83
|
+
The OpenAI Node client always sends `Authorization: Bearer <apiKey>`. If you put a raw `sk-...` value in `LLM_DEFAULT_HEADERS` / `OPENAI_DEFAULT_HEADERS` as `Authorization` **without** `Bearer`, many gateways treat that as the final header and return errors like `401` with `param: api_key`. When `LLM_API_KEY` / `OPENAI_API_KEY` is unset, this plugin detects `Authorization: sk-...` or `Authorization: Bearer sk-...` in the merged JSON headers, moves that token into the client `apiKey` (so the SDK sends a proper `Bearer` value), and removes the duplicate `Authorization` entry from `defaultHeaders` while keeping your other headers (for example `x-alfa-rbac`).
|
|
84
|
+
|
|
85
|
+
Prefer setting `LLM_API_KEY` (or `OPENAI_API_KEY`) to your `sk-...` token when your gateway documents API-key auth that way.
|
|
86
|
+
|
|
87
|
+
**PowerShell: company gateway**
|
|
88
|
+
|
|
89
|
+
```powershell
|
|
90
|
+
$env:LLM_BASE_URL = "https://llm-gateway.mycompany.example/v1"
|
|
91
|
+
$env:LLM_DEFAULT_HEADERS = '{"Authorization":"Bearer <token>","x-tenant-id":"salesforce"}'
|
|
92
|
+
sf sgai metadata summarize --from HEAD~1 --to HEAD
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### OpenAI (api.openai.com)
|
|
96
|
+
|
|
97
|
+
Set `OPENAI_API_KEY` only, or `LLM_API_KEY` if you standardize on `LLM_*` in your environment.
|
|
98
|
+
|
|
99
|
+
### Token limits
|
|
100
|
+
|
|
101
|
+
`OPENAI_MAX_TOKENS` caps `max_tokens`; `LLM_MAX_TOKENS` overrides it when set.
|
|
102
|
+
|
|
103
|
+
### Diff size (context window)
|
|
104
|
+
|
|
105
|
+
The full unified diff is capped before it is sent to the LLM so requests stay within typical context limits (for example 128k tokens). If you see errors about context length exceeded, narrow `--from`/`--to`, use `--message-filter`, or lower `--max-diff-chars` / `LLM_MAX_DIFF_CHARS`. Only raise the cap when your model and gateway allow a larger context.
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CommitInfo } from '../git/gitDiff.js';
|
|
2
|
+
import { type OpenAiLikeClient } from './openAiConfig.js';
|
|
3
|
+
/** Resolve max unified-diff characters for the LLM path. CLI wins, then env, then default. */
|
|
4
|
+
export declare function resolveLlmMaxDiffChars(cliOverride?: number): number;
|
|
5
|
+
export declare function truncateUnifiedDiffForLlm(diffText: string, maxChars: number): string;
|
|
6
|
+
export type SummarizeFlags = {
|
|
7
|
+
/** Start ref for the diff (required when invoking the CLI). */
|
|
8
|
+
from: string;
|
|
9
|
+
to?: string;
|
|
10
|
+
messageFilter?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
/** Optional team or squad label for the summary (CLI or env). */
|
|
13
|
+
team?: string;
|
|
14
|
+
/** Max characters of unified diff sent to the LLM (CLI); see `resolveLlmMaxDiffChars`. */
|
|
15
|
+
maxDiffChars?: number;
|
|
16
|
+
};
|
|
17
|
+
type OpenAiClientProvider = () => Promise<OpenAiLikeClient>;
|
|
18
|
+
export declare function generateSummary(diffText: string, fileNames: string[], commits: CommitInfo[], flags: SummarizeFlags, openAiClientProvider?: OpenAiClientProvider): Promise<string>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { createOpenAiLikeClient, shouldUseLlmGateway } from './openAiConfig.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cap on unified-diff characters sent to the LLM (only the diff body; preamble is extra).
|
|
4
|
+
* Tuned for ~128k-token context models; override with `LLM_MAX_DIFF_CHARS` or `--max-diff-chars`.
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_LLM_MAX_DIFF_CHARS = 120_000;
|
|
7
|
+
/** Resolve max unified-diff characters for the LLM path. CLI wins, then env, then default. */
|
|
8
|
+
export function resolveLlmMaxDiffChars(cliOverride) {
|
|
9
|
+
if (cliOverride !== undefined && Number.isFinite(cliOverride) && cliOverride > 0) {
|
|
10
|
+
return Math.trunc(cliOverride);
|
|
11
|
+
}
|
|
12
|
+
const raw = process.env.LLM_MAX_DIFF_CHARS?.trim();
|
|
13
|
+
if (raw) {
|
|
14
|
+
const parsed = Number.parseInt(raw, 10);
|
|
15
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
16
|
+
return parsed;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return DEFAULT_LLM_MAX_DIFF_CHARS;
|
|
20
|
+
}
|
|
21
|
+
export function truncateUnifiedDiffForLlm(diffText, maxChars) {
|
|
22
|
+
if (diffText.length <= maxChars) {
|
|
23
|
+
return diffText;
|
|
24
|
+
}
|
|
25
|
+
const marker = `\n\n--- TRUNCATED: unified diff was ${diffText.length} characters; only the first ${maxChars} were sent. Narrow the ref range, use --message-filter, or raise --max-diff-chars / LLM_MAX_DIFF_CHARS only if your model context allows. ---\n`;
|
|
26
|
+
return diffText.slice(0, maxChars) + marker;
|
|
27
|
+
}
|
|
28
|
+
/** System prompt for OpenAI when summarizing whatever git range / filter the user requested. */
|
|
29
|
+
const OPENAI_SYSTEM_PROMPT = `You are a senior Salesforce architect helping developers understand Salesforce metadata changes from the git context they supplied.
|
|
30
|
+
You receive: commit subject lines (when available), changed metadata paths, and unified git patch(es) scoped to Salesforce metadata package directories—either one range diff or concatenated per-commit patches, depending on how the diff was produced. Patches may be truncated mid-section with an explicit marker—do not infer changes beyond visible lines.
|
|
31
|
+
Explain what functionality changed: user-visible behavior, automations, integrations, data model, and security/access. Tie claims to the patch when possible.
|
|
32
|
+
Produce a concise, developer-focused summary in Markdown.
|
|
33
|
+
Use sections: Highlights, Risky or breaking changes, Data model changes, Automation & flows, Security & access.
|
|
34
|
+
Group related changes; do not list every individual component or file. When multiple commits appear in the context, briefly separate notable themes by commit when helpful.`;
|
|
35
|
+
function resolveSummaryTeam(flags) {
|
|
36
|
+
const fromFlag = flags.team?.trim();
|
|
37
|
+
if (fromFlag)
|
|
38
|
+
return fromFlag;
|
|
39
|
+
const auditTeam = process.env.METADATA_AUDIT_TEAM?.trim();
|
|
40
|
+
if (auditTeam)
|
|
41
|
+
return auditTeam;
|
|
42
|
+
const sfGitAiTeam = process.env.SF_GIT_AI_TEAM?.trim();
|
|
43
|
+
return sfGitAiTeam ?? undefined;
|
|
44
|
+
}
|
|
45
|
+
export async function generateSummary(diffText, fileNames, commits, flags, openAiClientProvider) {
|
|
46
|
+
const fileSection = fileNames.length
|
|
47
|
+
? fileNames.map((name) => `- ${name}`).join('\n')
|
|
48
|
+
: '- No metadata files changed.';
|
|
49
|
+
if (shouldUseLlmGateway()) {
|
|
50
|
+
const maxDiffChars = resolveLlmMaxDiffChars(flags.maxDiffChars);
|
|
51
|
+
const diffForLlm = truncateUnifiedDiffForLlm(diffText, maxDiffChars);
|
|
52
|
+
const userContent = buildOpenAiUserContent(flags, commits, fileNames, diffForLlm);
|
|
53
|
+
return callOpenAi(userContent, flags.model ?? 'gpt-4o-mini', openAiClientProvider ??
|
|
54
|
+
/* istanbul ignore next */ (async () => createOpenAiLikeClient()));
|
|
55
|
+
}
|
|
56
|
+
return buildFallbackSummary(fileSection, commits, diffText, flags);
|
|
57
|
+
}
|
|
58
|
+
function buildOpenAiUserContent(flags, commits, fileNames, diffText) {
|
|
59
|
+
const from = flags.from;
|
|
60
|
+
const to = flags.to ?? 'HEAD';
|
|
61
|
+
const team = resolveSummaryTeam(flags);
|
|
62
|
+
const ts = new Date().toISOString();
|
|
63
|
+
const teamLine = team ? `Team: ${team}\n` : '';
|
|
64
|
+
const filterLine = flags.messageFilter
|
|
65
|
+
? `Commit message filter (regex): ${flags.messageFilter}\nGit context shape: concatenated per-commit unified patches for matching commits only.\n`
|
|
66
|
+
: 'Commit message filter: none.\nGit context shape: single unified diff for the full ref range.\n';
|
|
67
|
+
const commitBlock = commits.length > 0
|
|
68
|
+
? commits.map((c) => `- ${c.hash.slice(0, 7)} ${c.message.replace(/\r?\n/g, ' ')}`).join('\n')
|
|
69
|
+
: '- (no commits in range after filtering)';
|
|
70
|
+
const pathsBlock = fileNames.length > 0 ? fileNames.join('\n') : '(no metadata paths in diff scope)';
|
|
71
|
+
return (`${teamLine}` +
|
|
72
|
+
`Date: ${ts}\n\n` +
|
|
73
|
+
`Git refs: ${from}..${to}\n` +
|
|
74
|
+
filterLine +
|
|
75
|
+
'\n' +
|
|
76
|
+
'=== Included commits (subject lines) ===\n' +
|
|
77
|
+
`${commitBlock}\n\n` +
|
|
78
|
+
'=== Changed metadata paths ===\n' +
|
|
79
|
+
`${pathsBlock}\n\n` +
|
|
80
|
+
'=== Git context (unified diff(s) scoped to Salesforce metadata paths; patches may be truncated with an explicit marker) ===\n' +
|
|
81
|
+
diffText);
|
|
82
|
+
}
|
|
83
|
+
function buildFallbackSummary(fileSection, commits, diffText, flags) {
|
|
84
|
+
const filterText = flags.messageFilter
|
|
85
|
+
? `Filtered by: \`${flags.messageFilter}\`\n\n`
|
|
86
|
+
: 'No commit message filter applied.\n\n';
|
|
87
|
+
const team = resolveSummaryTeam(flags);
|
|
88
|
+
const teamSection = team ? `## Team\n${team}\n\n` : '';
|
|
89
|
+
return ('# Metadata Change Summary\n\n' +
|
|
90
|
+
'## Range\n' +
|
|
91
|
+
`From: ${flags.from}\n` +
|
|
92
|
+
`To: ${flags.to ?? 'HEAD'}\n\n` +
|
|
93
|
+
teamSection +
|
|
94
|
+
'## Commit Filter\n' +
|
|
95
|
+
filterText +
|
|
96
|
+
`## Changed Files\n${fileSection}\n\n` +
|
|
97
|
+
'## Local Summary\n' +
|
|
98
|
+
'No LLM gateway was configured (set `OPENAI_API_KEY` or `LLM_API_KEY`, `OPENAI_BASE_URL` or `LLM_BASE_URL`, and/or `OPENAI_DEFAULT_HEADERS` / `LLM_DEFAULT_HEADERS`), so this is a local summary instead.\n\n' +
|
|
99
|
+
'### Commits\n' +
|
|
100
|
+
commits.map((commit) => `- ${commit.hash.slice(0, 7)} ${commit.message}`).join('\n') +
|
|
101
|
+
`\n\n### Diff Snippet\n\n${diffText.substring(0, 10_000)}`);
|
|
102
|
+
}
|
|
103
|
+
async function callOpenAi(userContent, model, openAiClientProvider) {
|
|
104
|
+
const client = await openAiClientProvider();
|
|
105
|
+
const maxTokensRaw = process.env.LLM_MAX_TOKENS ?? process.env.OPENAI_MAX_TOKENS;
|
|
106
|
+
const parsed = maxTokensRaw !== undefined ? Number.parseInt(maxTokensRaw, 10) : 4000;
|
|
107
|
+
const maxTokens = Number.isFinite(parsed) && parsed > 0 ? parsed : 4000;
|
|
108
|
+
const response = await client.chat.completions.create({
|
|
109
|
+
model,
|
|
110
|
+
messages: [
|
|
111
|
+
{
|
|
112
|
+
role: 'system',
|
|
113
|
+
content: OPENAI_SYSTEM_PROMPT,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
role: 'user',
|
|
117
|
+
content: userContent,
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
temperature: 0.2,
|
|
121
|
+
// OpenAI Chat Completions API uses snake_case for this field.
|
|
122
|
+
// eslint-disable-next-line camelcase -- matches OpenAI request body
|
|
123
|
+
max_tokens: maxTokens,
|
|
124
|
+
});
|
|
125
|
+
const typedResponse = response;
|
|
126
|
+
const text = typedResponse.choices?.[0]?.message?.content?.trim() ?? '';
|
|
127
|
+
return text.length > 0 ? text : 'No summary generated by OpenAI.';
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=metadataSummary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadataSummary.js","sourceRoot":"","sources":["../../src/ai/metadataSummary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible LLM gateways (OpenAI.com, Azure OpenAI, corporate proxies, etc.).
|
|
3
|
+
* `LLM_*` environment variables override matching `OPENAI_*` defaults where applicable.
|
|
4
|
+
*/
|
|
5
|
+
/** `LLM_BASE_URL` overrides `OPENAI_BASE_URL` when set. */
|
|
6
|
+
export declare function resolveLlmBaseUrl(): string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Merged default headers: `OPENAI_DEFAULT_HEADERS` first, then `LLM_DEFAULT_HEADERS` overrides.
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseLlmDefaultHeadersFromEnv(): Record<string, string> | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* OpenAI's Node SDK always sends `Authorization: Bearer ${apiKey}`.
|
|
13
|
+
* If `Authorization` is only present in `defaultHeaders` as a raw `sk-...` token (no Bearer),
|
|
14
|
+
* that header overrides the SDK value and many OpenAI-compatible gateways reject the request
|
|
15
|
+
* (`param: api_key`). When no `LLM_API_KEY` / `OPENAI_API_KEY` is set, promote recognizable
|
|
16
|
+
* tokens from `Authorization` into `apiKey` and drop that header from `defaultHeaders`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function splitPromotableAuthorizationFromHeaders(headers: Record<string, string>): {
|
|
19
|
+
defaultHeaders: Record<string, string>;
|
|
20
|
+
apiKeyFromAuthHeader?: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function shouldUseLlmGateway(): boolean;
|
|
23
|
+
export type OpenAiLikeClient = {
|
|
24
|
+
chat: {
|
|
25
|
+
completions: {
|
|
26
|
+
create(...options: unknown[]): Promise<unknown>;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/** Constructor-style options for the official OpenAI Node SDK (for tests and `createOpenAiLikeClient`). */
|
|
31
|
+
export type OpenAiLikeClientInit = {
|
|
32
|
+
apiKey: string;
|
|
33
|
+
baseURL?: string;
|
|
34
|
+
defaultHeaders?: Record<string, string>;
|
|
35
|
+
};
|
|
36
|
+
export declare function resolveOpenAiLikeClientInit(): OpenAiLikeClientInit;
|
|
37
|
+
/** Build options for `new OpenAI(...)` (official OpenAI Node SDK). */
|
|
38
|
+
export declare function createOpenAiLikeClient(): Promise<OpenAiLikeClient>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible LLM gateways (OpenAI.com, Azure OpenAI, corporate proxies, etc.).
|
|
3
|
+
* `LLM_*` environment variables override matching `OPENAI_*` defaults where applicable.
|
|
4
|
+
*/
|
|
5
|
+
/** `LLM_BASE_URL` overrides `OPENAI_BASE_URL` when set. */
|
|
6
|
+
export function resolveLlmBaseUrl() {
|
|
7
|
+
return process.env.LLM_BASE_URL?.trim() ?? process.env.OPENAI_BASE_URL?.trim();
|
|
8
|
+
}
|
|
9
|
+
function parseHeaderJsonObject(raw) {
|
|
10
|
+
const trimmed = raw?.trim();
|
|
11
|
+
if (!trimmed)
|
|
12
|
+
return {};
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(trimmed);
|
|
15
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
const out = {};
|
|
19
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
20
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
21
|
+
out[key] = value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Merged default headers: `OPENAI_DEFAULT_HEADERS` first, then `LLM_DEFAULT_HEADERS` overrides.
|
|
32
|
+
*/
|
|
33
|
+
export function parseLlmDefaultHeadersFromEnv() {
|
|
34
|
+
const base = parseHeaderJsonObject(process.env.OPENAI_DEFAULT_HEADERS);
|
|
35
|
+
const override = parseHeaderJsonObject(process.env.LLM_DEFAULT_HEADERS);
|
|
36
|
+
const merged = { ...base, ...override };
|
|
37
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
38
|
+
}
|
|
39
|
+
function findAuthorizationHeaderName(headers) {
|
|
40
|
+
return Object.keys(headers).find((k) => k.toLowerCase() === 'authorization');
|
|
41
|
+
}
|
|
42
|
+
/** Strip a single `Bearer <token>` prefix; otherwise return the trimmed value. */
|
|
43
|
+
function stripBearerPrefix(value) {
|
|
44
|
+
const trimmed = value.trim();
|
|
45
|
+
const match = /^Bearer\s+(\S+)/i.exec(trimmed);
|
|
46
|
+
return match?.[1] ?? trimmed;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* OpenAI's Node SDK always sends `Authorization: Bearer ${apiKey}`.
|
|
50
|
+
* If `Authorization` is only present in `defaultHeaders` as a raw `sk-...` token (no Bearer),
|
|
51
|
+
* that header overrides the SDK value and many OpenAI-compatible gateways reject the request
|
|
52
|
+
* (`param: api_key`). When no `LLM_API_KEY` / `OPENAI_API_KEY` is set, promote recognizable
|
|
53
|
+
* tokens from `Authorization` into `apiKey` and drop that header from `defaultHeaders`.
|
|
54
|
+
*/
|
|
55
|
+
export function splitPromotableAuthorizationFromHeaders(headers) {
|
|
56
|
+
const authName = findAuthorizationHeaderName(headers);
|
|
57
|
+
if (!authName) {
|
|
58
|
+
return { defaultHeaders: headers };
|
|
59
|
+
}
|
|
60
|
+
const raw = headers[authName];
|
|
61
|
+
if (!raw) {
|
|
62
|
+
return { defaultHeaders: headers };
|
|
63
|
+
}
|
|
64
|
+
const token = stripBearerPrefix(raw);
|
|
65
|
+
const looksBearer = /^Bearer\s+\S+/i.test(raw.trim());
|
|
66
|
+
const looksOpenAiKey = /^sk-/i.test(token);
|
|
67
|
+
if (!looksBearer && !looksOpenAiKey) {
|
|
68
|
+
return { defaultHeaders: headers };
|
|
69
|
+
}
|
|
70
|
+
const next = { ...headers };
|
|
71
|
+
delete next[authName];
|
|
72
|
+
return { defaultHeaders: next, apiKeyFromAuthHeader: token };
|
|
73
|
+
}
|
|
74
|
+
export function shouldUseLlmGateway() {
|
|
75
|
+
const apiKey = process.env.LLM_API_KEY?.trim() ?? process.env.OPENAI_API_KEY?.trim();
|
|
76
|
+
if (apiKey)
|
|
77
|
+
return true;
|
|
78
|
+
if (resolveLlmBaseUrl())
|
|
79
|
+
return true;
|
|
80
|
+
const jsonHeaders = parseLlmDefaultHeadersFromEnv();
|
|
81
|
+
if (jsonHeaders && Object.keys(jsonHeaders).length > 0)
|
|
82
|
+
return true;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
export function resolveOpenAiLikeClientInit() {
|
|
86
|
+
const baseURL = resolveLlmBaseUrl();
|
|
87
|
+
const mergedHeaders = parseLlmDefaultHeadersFromEnv() ?? {};
|
|
88
|
+
const envApiKey = process.env.LLM_API_KEY?.trim() ?? process.env.OPENAI_API_KEY?.trim() ?? '';
|
|
89
|
+
let defaultHeaders;
|
|
90
|
+
let apiKey = envApiKey;
|
|
91
|
+
if (apiKey.length === 0) {
|
|
92
|
+
const split = splitPromotableAuthorizationFromHeaders(mergedHeaders);
|
|
93
|
+
if (split.apiKeyFromAuthHeader) {
|
|
94
|
+
apiKey = split.apiKeyFromAuthHeader;
|
|
95
|
+
}
|
|
96
|
+
defaultHeaders = Object.keys(split.defaultHeaders).length > 0 ? split.defaultHeaders : undefined;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
defaultHeaders = Object.keys(mergedHeaders).length > 0 ? mergedHeaders : undefined;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
apiKey: apiKey.length > 0 ? apiKey : 'unused',
|
|
103
|
+
...(baseURL ? { baseURL } : {}),
|
|
104
|
+
...(defaultHeaders ? { defaultHeaders } : {}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Build options for `new OpenAI(...)` (official OpenAI Node SDK). */
|
|
108
|
+
export async function createOpenAiLikeClient() {
|
|
109
|
+
const { default: OpenAI } = await import('openai');
|
|
110
|
+
return new OpenAI(resolveOpenAiLikeClientInit());
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=openAiConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openAiConfig.js","sourceRoot":"","sources":["../../src/ai/openAiConfig.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SfCommand } from '@salesforce/sf-plugins-core';
|
|
2
|
+
export type SgaiMetadataSummarizeResult = {
|
|
3
|
+
path: string;
|
|
4
|
+
};
|
|
5
|
+
export default class SgaiMetadataSummarize extends SfCommand<SgaiMetadataSummarizeResult> {
|
|
6
|
+
static readonly summary: string;
|
|
7
|
+
static readonly description: string;
|
|
8
|
+
static readonly examples: string[];
|
|
9
|
+
static readonly flags: {
|
|
10
|
+
from: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
to: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'message-filter': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
team: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
model: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
'max-diff-chars': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
'ignore-package-directory': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
};
|
|
19
|
+
run(): Promise<SgaiMetadataSummarizeResult>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
|
|
3
|
+
import { Messages, SfError } from '@salesforce/core';
|
|
4
|
+
import { createGitClient, filterCommits, getCommits, getDiff, getChangedFiles } from '../../../git/gitDiff.js';
|
|
5
|
+
import { generateSummary } from '../../../ai/metadataSummary.js';
|
|
6
|
+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
7
|
+
const messages = Messages.loadMessages('sf-git-ai-meta-insights', 'sgai.metadata.summarize');
|
|
8
|
+
export default class SgaiMetadataSummarize extends SfCommand {
|
|
9
|
+
static summary = messages.getMessage('summary');
|
|
10
|
+
static description = messages.getMessage('description');
|
|
11
|
+
static examples = messages.getMessages('examples');
|
|
12
|
+
static flags = {
|
|
13
|
+
from: Flags.string({
|
|
14
|
+
summary: messages.getMessage('flags.from.summary'),
|
|
15
|
+
description: messages.getMessage('flags.from.description'),
|
|
16
|
+
char: 'f',
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
to: Flags.string({
|
|
20
|
+
summary: messages.getMessage('flags.to.summary'),
|
|
21
|
+
description: messages.getMessage('flags.to.description'),
|
|
22
|
+
char: 't',
|
|
23
|
+
required: false,
|
|
24
|
+
}),
|
|
25
|
+
'message-filter': Flags.string({
|
|
26
|
+
summary: messages.getMessage('flags.message-filter.summary'),
|
|
27
|
+
description: messages.getMessage('flags.message-filter.description'),
|
|
28
|
+
char: 'm',
|
|
29
|
+
required: false,
|
|
30
|
+
}),
|
|
31
|
+
team: Flags.string({
|
|
32
|
+
summary: messages.getMessage('flags.team.summary'),
|
|
33
|
+
description: messages.getMessage('flags.team.description'),
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
output: Flags.string({
|
|
37
|
+
summary: messages.getMessage('flags.output.summary'),
|
|
38
|
+
description: messages.getMessage('flags.output.description'),
|
|
39
|
+
char: 'p',
|
|
40
|
+
required: false,
|
|
41
|
+
default: 'metadata-summary.md',
|
|
42
|
+
}),
|
|
43
|
+
model: Flags.string({
|
|
44
|
+
summary: messages.getMessage('flags.model.summary'),
|
|
45
|
+
description: messages.getMessage('flags.model.description'),
|
|
46
|
+
required: false,
|
|
47
|
+
default: 'gpt-4o-mini',
|
|
48
|
+
}),
|
|
49
|
+
'max-diff-chars': Flags.integer({
|
|
50
|
+
summary: messages.getMessage('flags.max-diff-chars.summary'),
|
|
51
|
+
description: messages.getMessage('flags.max-diff-chars.description'),
|
|
52
|
+
required: false,
|
|
53
|
+
}),
|
|
54
|
+
'ignore-package-directory': Flags.directory({
|
|
55
|
+
summary: messages.getMessage('flags.ignore-package-directory.summary'),
|
|
56
|
+
description: messages.getMessage('flags.ignore-package-directory.description'),
|
|
57
|
+
char: 'i',
|
|
58
|
+
required: false,
|
|
59
|
+
multiple: true,
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
async run() {
|
|
63
|
+
const { flags } = await this.parse(SgaiMetadataSummarize);
|
|
64
|
+
const maxDiffCharsFlag = flags['max-diff-chars'];
|
|
65
|
+
if (maxDiffCharsFlag !== undefined && (maxDiffCharsFlag < 5000 || maxDiffCharsFlag > 5_000_000)) {
|
|
66
|
+
throw new SfError(`--max-diff-chars must be between 5000 and 5,000,000 (received ${maxDiffCharsFlag}).`, 'InvalidMaxDiffChars');
|
|
67
|
+
}
|
|
68
|
+
const from = flags.from;
|
|
69
|
+
const to = flags.to ?? 'HEAD';
|
|
70
|
+
const outputPath = flags.output ?? 'metadata-summary.md';
|
|
71
|
+
const ignorePackageDirectories = Array.isArray(flags['ignore-package-directory'])
|
|
72
|
+
? flags['ignore-package-directory'].filter(Boolean)
|
|
73
|
+
: flags['ignore-package-directory']
|
|
74
|
+
? [flags['ignore-package-directory']]
|
|
75
|
+
: undefined;
|
|
76
|
+
const git = createGitClient(process.cwd());
|
|
77
|
+
const commits = await getCommits(git, from, to);
|
|
78
|
+
let filteredCommits;
|
|
79
|
+
try {
|
|
80
|
+
filteredCommits = filterCommits(commits, flags['message-filter']);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
84
|
+
if (message.includes('Invalid commit message filter')) {
|
|
85
|
+
throw new SfError(message, 'InvalidMessageFilter');
|
|
86
|
+
}
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
if (flags['message-filter'] && filteredCommits.length === 0) {
|
|
90
|
+
const message = `No commits matched the filter '${flags['message-filter']}' between ${from} and ${to}.`;
|
|
91
|
+
await writeFile(outputPath, `# Metadata Summary\n\n${message}\n`, 'utf8');
|
|
92
|
+
this.log(`Generated metadata summary at ${outputPath}`);
|
|
93
|
+
return { path: outputPath };
|
|
94
|
+
}
|
|
95
|
+
const diffText = await getDiff(git, from, to, filteredCommits, Boolean(flags['message-filter']), ignorePackageDirectories);
|
|
96
|
+
const fileNames = await getChangedFiles(git, from, to, filteredCommits, Boolean(flags['message-filter']), ignorePackageDirectories);
|
|
97
|
+
const summary = await generateSummary(diffText, fileNames, filteredCommits, {
|
|
98
|
+
from,
|
|
99
|
+
to: flags.to,
|
|
100
|
+
messageFilter: flags['message-filter'],
|
|
101
|
+
model: flags.model,
|
|
102
|
+
team: flags.team,
|
|
103
|
+
maxDiffChars: maxDiffCharsFlag,
|
|
104
|
+
});
|
|
105
|
+
await writeFile(outputPath, summary, 'utf8');
|
|
106
|
+
this.log(`Generated metadata summary at ${outputPath}`);
|
|
107
|
+
return { path: outputPath };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=summarize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../../../src/commands/sgai/metadata/summarize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SimpleGit } from 'simple-git';
|
|
2
|
+
export type CommitInfo = {
|
|
3
|
+
hash: string;
|
|
4
|
+
message: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function createGitClient(cwd?: string): SimpleGit;
|
|
7
|
+
export declare function getCommits(git: SimpleGit, from: string, to: string): Promise<CommitInfo[]>;
|
|
8
|
+
export declare function filterCommits(commits: CommitInfo[], messageFilter?: string): CommitInfo[];
|
|
9
|
+
export declare function getRepoRoot(git: SimpleGit): Promise<string>;
|
|
10
|
+
export declare function getDiff(git: SimpleGit, from: string, to: string, commits: CommitInfo[], filterByCommits: boolean, ignorePackageDirectories?: string[], repoRootOverride?: string): Promise<string>;
|
|
11
|
+
export declare function getChangedFiles(git: SimpleGit, from: string, to: string, commits: CommitInfo[], filterByCommits: boolean, ignorePackageDirectories?: string[], repoRootOverride?: string): Promise<string[]>;
|