@xbzbing/dsh-git-panel 1.6.1 → 1.6.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.en.md +3 -3
- package/README.md +3 -3
- package/lib/client.js +12 -7
- package/lib/host/core.d.ts +9 -1
- package/lib/host/core.js +8 -1
- package/lib/host/index.d.ts +5 -3
- package/lib/host/index.js +11 -2
- package/lib/host/suggest.d.ts +20 -1
- package/lib/host/suggest.js +78 -16
- package/lib/host/types.d.ts +19 -4
- package/lib/host/types.js +4 -0
- package/package.json +1 -1
package/lib/host/core.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GitRunner } from './git.js';
|
|
2
2
|
import type { AgentDefaultModelFace, LlmFace } from './llm-face.js';
|
|
3
|
-
import type { DiffViewMode, GraphStyle, GitChange, GitErrorCode, GitRemote, GitSnapshotResult } from './types.js';
|
|
3
|
+
import type { DiffViewMode, GraphStyle, GitChange, GitErrorCode, GitRemote, GitSnapshotResult, SuggestLanguage } from './types.js';
|
|
4
4
|
export interface GitPanelConfig {
|
|
5
5
|
readonly timeoutMs: number;
|
|
6
6
|
/** Per-command stdout cap; also the per-side image-diff payload cap. */
|
|
@@ -15,6 +15,8 @@ export interface GitPanelConfig {
|
|
|
15
15
|
readonly graphStyle: GraphStyle;
|
|
16
16
|
/** Whether the "AI suggest" button shows in the commit box. */
|
|
17
17
|
readonly suggestEnabled: boolean;
|
|
18
|
+
/** Language the generated message is written in; `auto` follows repo history. */
|
|
19
|
+
readonly suggestLanguage: SuggestLanguage;
|
|
18
20
|
/** Max diff bytes sent to the model; beyond this the diff is truncated. Range 16–512 KiB. */
|
|
19
21
|
readonly suggestMaxBytes: number;
|
|
20
22
|
/** Model output token budget (reasoning tokens count against it on thinking models). Range 256–16384. */
|
|
@@ -31,6 +33,12 @@ export declare function normalizeConfig(raw: unknown): GitPanelConfig;
|
|
|
31
33
|
export declare function readDiffView(value: unknown, fallback: DiffViewMode): DiffViewMode;
|
|
32
34
|
/** Read the commit-graph style field, unwrapping a schemastery volatile ref. */
|
|
33
35
|
export declare function readGraphStyle(value: unknown, fallback: GraphStyle): GraphStyle;
|
|
36
|
+
/**
|
|
37
|
+
* Read the commit-message language field, unwrapping a schemastery volatile
|
|
38
|
+
* ref. An unknown string (a hand-edited profile, or a value from a newer
|
|
39
|
+
* version) falls back to the default rather than reaching the prompt.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readSuggestLanguage(value: unknown, fallback: SuggestLanguage): SuggestLanguage;
|
|
34
42
|
/**
|
|
35
43
|
* Read a boolean config field, unwrapping a schemastery volatile reference
|
|
36
44
|
* (`{ get() }`) so a live-editable toggle reflects the latest value. Absent or
|
package/lib/host/core.js
CHANGED
|
@@ -2,7 +2,7 @@ import { dirname, join } from "node:path";
|
|
|
2
2
|
import { commitFromFields, parseStatus, sumNumstat } from "./parser.js";
|
|
3
3
|
import { parseRemote } from "./remote.js";
|
|
4
4
|
import { isSafePath } from "./validate.js";
|
|
5
|
-
import { SUGGEST_LIMITS } from "./types.js";
|
|
5
|
+
import { SUGGEST_LANGUAGE_DEFAULT, SUGGEST_LANGUAGES, SUGGEST_LIMITS } from "./types.js";
|
|
6
6
|
const DEFAULT_CONFIG = {
|
|
7
7
|
timeoutMs: 8e3,
|
|
8
8
|
maxBytes: 4 * 1024 * 1024,
|
|
@@ -12,6 +12,7 @@ const DEFAULT_CONFIG = {
|
|
|
12
12
|
defaultDiffView: "unified",
|
|
13
13
|
graphStyle: "parallel",
|
|
14
14
|
suggestEnabled: true,
|
|
15
|
+
suggestLanguage: SUGGEST_LANGUAGE_DEFAULT,
|
|
15
16
|
suggestMaxBytes: SUGGEST_LIMITS.maxBytes.default,
|
|
16
17
|
suggestMaxOutputTokens: SUGGEST_LIMITS.maxOutputTokens.default,
|
|
17
18
|
suggestTimeoutMs: SUGGEST_LIMITS.timeoutMs.default
|
|
@@ -35,6 +36,7 @@ function normalizeConfig(raw) {
|
|
|
35
36
|
defaultDiffView: readDiffView(c.defaultDiffView, DEFAULT_CONFIG.defaultDiffView),
|
|
36
37
|
graphStyle: readGraphStyle(c.graphStyle, DEFAULT_CONFIG.graphStyle),
|
|
37
38
|
suggestEnabled: readBool(c.suggestEnabled, DEFAULT_CONFIG.suggestEnabled),
|
|
39
|
+
suggestLanguage: readSuggestLanguage(c.suggestLanguage, DEFAULT_CONFIG.suggestLanguage),
|
|
38
40
|
suggestMaxBytes: clampLimit(c.suggestMaxBytes, SUGGEST_LIMITS.maxBytes),
|
|
39
41
|
suggestMaxOutputTokens: clampLimit(c.suggestMaxOutputTokens, SUGGEST_LIMITS.maxOutputTokens),
|
|
40
42
|
suggestTimeoutMs: clampLimit(c.suggestTimeoutMs, SUGGEST_LIMITS.timeoutMs),
|
|
@@ -52,6 +54,10 @@ function readGraphStyle(value, fallback) {
|
|
|
52
54
|
const raw = unwrapVolatile(value);
|
|
53
55
|
return raw === "parallel" || raw === "compact" ? raw : fallback;
|
|
54
56
|
}
|
|
57
|
+
function readSuggestLanguage(value, fallback) {
|
|
58
|
+
const raw = unwrapVolatile(value);
|
|
59
|
+
return typeof raw === "string" && SUGGEST_LANGUAGES.includes(raw) ? raw : fallback;
|
|
60
|
+
}
|
|
55
61
|
function readBool(value, fallback) {
|
|
56
62
|
const raw = unwrapVolatile(value);
|
|
57
63
|
return typeof raw === "boolean" ? raw : fallback;
|
|
@@ -298,6 +304,7 @@ export {
|
|
|
298
304
|
readBool,
|
|
299
305
|
readDiffView,
|
|
300
306
|
readGraphStyle,
|
|
307
|
+
readSuggestLanguage,
|
|
301
308
|
resolveBrowseRoot,
|
|
302
309
|
resolveWorkspace,
|
|
303
310
|
runCommand,
|
package/lib/host/index.d.ts
CHANGED
|
@@ -10,14 +10,14 @@ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
11
|
import Schema from '@deepseek-ai/schemastery';
|
|
12
12
|
import type { GitActionRequest, GitActionResult, GitQueryRequest, GitQueryResponse, GitSnapshotRequest, GitSnapshotResult, GitSuggestRequest, GitSuggestResult, GitVersionInfo, GitVersionRequest } from './types.js';
|
|
13
|
-
export type { GitSnapshot, GitSnapshotResult, GitSnapshotRequest, GitFailure, GitCommit, GraphCommit, GitRef, GitChange, GitChangeStatus, GitAction, GitActionRequest, GitActionResult, GitErrorCode, GitQuery, GitQueryRequest, GitQueryResponse, GitQueryResult, GitBranch, GitFileStat, WorktreeStats, StashEntry, GitVersionRequest, GitVersionInfo, DiffViewMode, GraphStyle, DirEntry, ResetMode, GitSuggestRequest, GitSuggestResult, GitRemote, RemoteHostKind, } from './types.js';
|
|
14
|
-
export { normalizeConfig, DEFAULT_CONFIG, snapshotForSession, resolveWorkspace, parsePrimaryRemote } from './core.js';
|
|
13
|
+
export type { GitSnapshot, GitSnapshotResult, GitSnapshotRequest, GitFailure, GitCommit, GraphCommit, GitRef, GitChange, GitChangeStatus, GitAction, GitActionRequest, GitActionResult, GitErrorCode, GitQuery, GitQueryRequest, GitQueryResponse, GitQueryResult, GitBranch, GitFileStat, WorktreeStats, StashEntry, GitVersionRequest, GitVersionInfo, DiffViewMode, GraphStyle, DirEntry, ResetMode, GitSuggestRequest, GitSuggestResult, GitRemote, RemoteHostKind, SuggestLanguage, ResolvedLanguage, } from './types.js';
|
|
14
|
+
export { normalizeConfig, DEFAULT_CONFIG, snapshotForSession, resolveWorkspace, parsePrimaryRemote, readSuggestLanguage } from './core.js';
|
|
15
15
|
export { createGitRunner } from './git.js';
|
|
16
16
|
export { parseStatus, parseGraphLog, parseBranches, parseNameStatus, parseStashList, parseTags, sumNumstat } from './parser.js';
|
|
17
17
|
export { parseRemote, classifyHost } from './remote.js';
|
|
18
18
|
export { isSafePath, planAction, runAction, classifyActionFailure } from './actions.js';
|
|
19
19
|
export { runQuery } from './queries.js';
|
|
20
|
-
export { runSuggest } from './suggest.js';
|
|
20
|
+
export { runSuggest, resolveSuggestLanguage, parseStyleHistory, isCjkText } from './suggest.js';
|
|
21
21
|
export { readVersionInfo, checkLatestVersion, compareVersions, parseRepository } from './version.js';
|
|
22
22
|
export declare class GitPanelService extends TypertRemoteService {
|
|
23
23
|
static inject: string[];
|
|
@@ -32,6 +32,7 @@ export declare class GitPanelService extends TypertRemoteService {
|
|
|
32
32
|
defaultDiffView: Schema<"split" | "unified", "split" | "unified", "volatile-defined">;
|
|
33
33
|
graphStyle: Schema<"compact" | "parallel", "compact" | "parallel", "volatile-defined">;
|
|
34
34
|
suggestEnabled: Schema<boolean, boolean, "volatile-defined">;
|
|
35
|
+
suggestLanguage: Schema<"auto" | "zh" | "en", "auto" | "zh" | "en", "volatile-defined">;
|
|
35
36
|
suggestMaxBytes: Schema<number, number, "volatile-defined">;
|
|
36
37
|
suggestMaxOutputTokens: Schema<number, number, "volatile-defined">;
|
|
37
38
|
suggestTimeoutMs: Schema<number, number, "volatile-defined">;
|
|
@@ -40,6 +41,7 @@ export declare class GitPanelService extends TypertRemoteService {
|
|
|
40
41
|
defaultDiffView: Schema<"split" | "unified", "split" | "unified", "volatile-defined">;
|
|
41
42
|
graphStyle: Schema<"compact" | "parallel", "compact" | "parallel", "volatile-defined">;
|
|
42
43
|
suggestEnabled: Schema<boolean, boolean, "volatile-defined">;
|
|
44
|
+
suggestLanguage: Schema<"auto" | "zh" | "en", "auto" | "zh" | "en", "volatile-defined">;
|
|
43
45
|
suggestMaxBytes: Schema<number, number, "volatile-defined">;
|
|
44
46
|
suggestMaxOutputTokens: Schema<number, number, "volatile-defined">;
|
|
45
47
|
suggestTimeoutMs: Schema<number, number, "volatile-defined">;
|
package/lib/host/index.js
CHANGED
|
@@ -56,13 +56,13 @@ import { runAction } from "./actions.js";
|
|
|
56
56
|
import { runQuery } from "./queries.js";
|
|
57
57
|
import { runSuggest } from "./suggest.js";
|
|
58
58
|
import { checkLatestVersion, readVersionInfo } from "./version.js";
|
|
59
|
-
import { normalizeConfig as normalizeConfig2, DEFAULT_CONFIG as DEFAULT_CONFIG2, snapshotForSession as snapshotForSession2, resolveWorkspace, parsePrimaryRemote } from "./core.js";
|
|
59
|
+
import { normalizeConfig as normalizeConfig2, DEFAULT_CONFIG as DEFAULT_CONFIG2, snapshotForSession as snapshotForSession2, resolveWorkspace, parsePrimaryRemote, readSuggestLanguage } from "./core.js";
|
|
60
60
|
import { createGitRunner as createGitRunner2 } from "./git.js";
|
|
61
61
|
import { parseStatus, parseGraphLog, parseBranches, parseNameStatus, parseStashList, parseTags, sumNumstat } from "./parser.js";
|
|
62
62
|
import { parseRemote, classifyHost } from "./remote.js";
|
|
63
63
|
import { isSafePath, planAction, runAction as runAction2, classifyActionFailure } from "./actions.js";
|
|
64
64
|
import { runQuery as runQuery2 } from "./queries.js";
|
|
65
|
-
import { runSuggest as runSuggest2 } from "./suggest.js";
|
|
65
|
+
import { runSuggest as runSuggest2, resolveSuggestLanguage, parseStyleHistory, isCjkText } from "./suggest.js";
|
|
66
66
|
import { readVersionInfo as readVersionInfo2, checkLatestVersion as checkLatestVersion2, compareVersions, parseRepository } from "./version.js";
|
|
67
67
|
class GitPanelService extends (_a = TypertRemoteService, _snapshot_dec = [Remote("snapshot")], _run_dec = [Remote("run")], _query_dec = [Remote("query")], _suggest_dec = [Remote("suggest")], _version_dec = [Remote("version")], _a) {
|
|
68
68
|
constructor(ctx, config) {
|
|
@@ -180,6 +180,11 @@ __publicField(GitPanelService, "Config", Schema.object({
|
|
|
180
180
|
Schema.const("compact").description("\u7D27\u51D1\uFF08git log \u98CE\u683C\uFF0C\u5171\u4EAB\u7956\u5148\u5408\u5E76\u4E3A\u4E00\u5217\uFF09")
|
|
181
181
|
]).default("parallel").volatile().description("\u63D0\u4EA4\u5386\u53F2\u56FE\u7EBF\u6761\u6837\u5F0F"),
|
|
182
182
|
suggestEnabled: Schema.boolean().default(true).volatile().description("\u63D0\u4EA4\u6846\u663E\u793A AI \u751F\u6210\u6309\u94AE"),
|
|
183
|
+
suggestLanguage: Schema.union([
|
|
184
|
+
Schema.const("auto").description("\u81EA\u52A8\u8DDF\u968F\u4ED3\u5E93\u5386\u53F2\u63D0\u4EA4\u7684\u8BED\u8A00"),
|
|
185
|
+
Schema.const("zh").description("\u4E2D\u6587"),
|
|
186
|
+
Schema.const("en").description("English")
|
|
187
|
+
]).default(DEFAULT_CONFIG.suggestLanguage).volatile().description("AI \u751F\u6210\u63D0\u4EA4\u4FE1\u606F\u7684\u8BED\u8A00"),
|
|
183
188
|
// Defaults come from DEFAULT_CONFIG (which itself reads SUGGEST_LIMITS) —
|
|
184
189
|
// a literal here could silently drift from what normalizeConfig enforces.
|
|
185
190
|
suggestMaxBytes: Schema.number().default(DEFAULT_CONFIG.suggestMaxBytes).volatile().description("AI \u751F\u6210\u8F93\u5165\u9884\u7B97\uFF08diff \u5B57\u8282\u4E0A\u9650\uFF0C16\u2013512 KiB\uFF09"),
|
|
@@ -196,6 +201,7 @@ export {
|
|
|
196
201
|
compareVersions,
|
|
197
202
|
createGitRunner2 as createGitRunner,
|
|
198
203
|
index_default as default,
|
|
204
|
+
isCjkText,
|
|
199
205
|
isSafePath,
|
|
200
206
|
normalizeConfig2 as normalizeConfig,
|
|
201
207
|
parseBranches,
|
|
@@ -206,9 +212,12 @@ export {
|
|
|
206
212
|
parseRepository,
|
|
207
213
|
parseStashList,
|
|
208
214
|
parseStatus,
|
|
215
|
+
parseStyleHistory,
|
|
209
216
|
parseTags,
|
|
210
217
|
planAction,
|
|
218
|
+
readSuggestLanguage,
|
|
211
219
|
readVersionInfo2 as readVersionInfo,
|
|
220
|
+
resolveSuggestLanguage,
|
|
212
221
|
resolveWorkspace,
|
|
213
222
|
runAction2 as runAction,
|
|
214
223
|
runQuery2 as runQuery,
|
package/lib/host/suggest.d.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
import type { GitPanelConfig, SnapshotDeps } from './core.js';
|
|
2
|
-
import type { GitSuggestRequest, GitSuggestResult } from './types.js';
|
|
2
|
+
import type { GitSuggestRequest, GitSuggestResult, ResolvedLanguage, SuggestLanguage } from './types.js';
|
|
3
|
+
export declare function isCjkText(text: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Parse `git log -z --format=%ae%x1f%s` into the repository's own commit
|
|
6
|
+
* subjects, newest first: mechanical commits dropped, and the current
|
|
7
|
+
* author's commits preferred once they are numerous enough to represent the
|
|
8
|
+
* repository (a shared checkout may mix identities). Subjects are trimmed to
|
|
9
|
+
* `STYLE_SAMPLE_MAX_CHARS` code points so one runaway subject cannot consume
|
|
10
|
+
* the generation budget; the closest commit's language still dominates.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseStyleHistory(stdout: string, ownEmail: string | undefined): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the language a suggestion is written in. `zh` / `en` are honored as
|
|
15
|
+
* configured; `auto` votes over the repository's recent non-mechanical commit
|
|
16
|
+
* subjects with a recency decay, so a repository that switched languages is
|
|
17
|
+
* judged by how it writes *now* rather than by its whole history. With no
|
|
18
|
+
* history to read (an unborn repository) the vote is empty and English is the
|
|
19
|
+
* neutral fallback.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveSuggestLanguage(setting: SuggestLanguage, samples: readonly string[]): ResolvedLanguage;
|
|
3
22
|
/** Generate a commit message for the session's uncommitted changes. */
|
|
4
23
|
export declare function runSuggest(deps: SnapshotDeps, config: GitPanelConfig, request: GitSuggestRequest): Promise<GitSuggestResult>;
|
package/lib/host/suggest.js
CHANGED
|
@@ -1,14 +1,63 @@
|
|
|
1
1
|
import { mapWorkspaceFailure, resolveWorkspace, runCommand } from "./core.js";
|
|
2
2
|
import { isSafePath } from "./validate.js";
|
|
3
3
|
import { parseStatus } from "./parser.js";
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
const HISTORY_SCAN = 60;
|
|
5
|
+
const STYLE_SAMPLE_CAP = 12;
|
|
6
|
+
const STYLE_SAMPLE_MAX_CHARS = 200;
|
|
7
|
+
const OWN_AUTHOR_MIN = 3;
|
|
8
|
+
const LANGUAGE_DECAY = 0.9;
|
|
9
|
+
const MECHANICAL_SUBJECT = /^(chore: release|merged?\b|revert\b|fixup!|squash!)/i;
|
|
10
|
+
const CJK = /[\u4e00-\u9fff]/;
|
|
11
|
+
function isCjkText(text) {
|
|
12
|
+
return CJK.test(text);
|
|
13
|
+
}
|
|
14
|
+
function parseStyleHistory(stdout, ownEmail) {
|
|
15
|
+
const records = [];
|
|
16
|
+
for (const record of stdout.split("\0")) {
|
|
17
|
+
const sep = record.indexOf("");
|
|
18
|
+
if (sep === -1) continue;
|
|
19
|
+
const subject = record.slice(sep + 1).trim();
|
|
20
|
+
if (subject === "" || MECHANICAL_SUBJECT.test(subject)) continue;
|
|
21
|
+
records.push({ email: record.slice(0, sep), subject });
|
|
22
|
+
}
|
|
23
|
+
const own = ownEmail === void 0 || ownEmail === "" ? [] : records.filter((r) => r.email === ownEmail);
|
|
24
|
+
const pool = own.length >= OWN_AUTHOR_MIN ? own : records;
|
|
25
|
+
return pool.slice(0, STYLE_SAMPLE_CAP).map((r) => clampSubject(r.subject));
|
|
26
|
+
}
|
|
27
|
+
function clampSubject(subject) {
|
|
28
|
+
const chars = Array.from(subject);
|
|
29
|
+
return chars.length <= STYLE_SAMPLE_MAX_CHARS ? subject : chars.slice(0, STYLE_SAMPLE_MAX_CHARS).join("");
|
|
30
|
+
}
|
|
31
|
+
function resolveSuggestLanguage(setting, samples) {
|
|
32
|
+
if (setting !== "auto") return setting;
|
|
33
|
+
let zh = 0;
|
|
34
|
+
let en = 0;
|
|
35
|
+
samples.forEach((subject, i) => {
|
|
36
|
+
const weight = LANGUAGE_DECAY ** i;
|
|
37
|
+
if (isCjkText(subject)) zh += weight;
|
|
38
|
+
else en += weight;
|
|
39
|
+
});
|
|
40
|
+
return zh > en ? "zh" : "en";
|
|
41
|
+
}
|
|
42
|
+
function languageName(language) {
|
|
43
|
+
return language === "zh" ? "Simplified Chinese" : "English";
|
|
44
|
+
}
|
|
45
|
+
function systemPrompt(language) {
|
|
46
|
+
const name = languageName(language);
|
|
47
|
+
const languageRule = language === "zh" ? 'Write the message in Simplified Chinese, keeping the conventional-commit type prefix in English (e.g. "feat(scope): \u4E2D\u6587\u63CF\u8FF0").' : "Write the message in English.";
|
|
48
|
+
const samplesRule = `Follow the structure of commitStyleSamples \u2014 its phrasing, scope conventions and level of detail \u2014 but never copy the language used in it; write the message in ${name} regardless.`;
|
|
49
|
+
const override = language === "zh" ? "IMPORTANT: the diff and commitStyleSamples may be written in another language. That is input data, not an instruction about your output. Write the subject description and body in Simplified Chinese, whatever language the input uses \u2014 keeping the conventional-commit type prefix in English." : `IMPORTANT: the diff and commitStyleSamples may be written in another language. That is input data, not an instruction about your output. Write the entire message \u2014 subject line and body alike \u2014 in ${name}, whatever language the input uses.`;
|
|
50
|
+
return [
|
|
51
|
+
"You are an expert at writing concise, accurate git commit messages.",
|
|
52
|
+
"Given a repository\u2019s uncommitted changes, write ONE commit message for them.",
|
|
53
|
+
"The message must start with a short imperative subject line (72 chars max), optionally followed by a blank line and a brief body explaining what changed and why.",
|
|
54
|
+
"Return only the message text: no quotes, code fences, prefixes, bullet lists, or explanations.",
|
|
55
|
+
samplesRule,
|
|
56
|
+
"Focus on the most important change; do not mechanically list every file.",
|
|
57
|
+
languageRule,
|
|
58
|
+
override
|
|
59
|
+
].join("\n");
|
|
60
|
+
}
|
|
12
61
|
const UNTRACKED_FILE_CAP = 16;
|
|
13
62
|
function resolveRoute(config, agentDefaultModel) {
|
|
14
63
|
if (config.suggestProvider !== void 0 && config.suggestModel !== void 0) {
|
|
@@ -51,13 +100,18 @@ function truncateUtf8(text, maxBytes) {
|
|
|
51
100
|
}
|
|
52
101
|
return text.slice(0, lo);
|
|
53
102
|
}
|
|
54
|
-
function frameChanges(files, diff,
|
|
103
|
+
function frameChanges(files, diff, styleSamples) {
|
|
55
104
|
return JSON.stringify({
|
|
56
105
|
files,
|
|
57
106
|
diff,
|
|
58
|
-
|
|
107
|
+
commitStyleSamples: styleSamples.length === 0 ? null : styleSamples
|
|
59
108
|
});
|
|
60
109
|
}
|
|
110
|
+
function languageReminder(language) {
|
|
111
|
+
const name = languageName(language);
|
|
112
|
+
const prefix = language === "zh" ? " Keep the conventional-commit type prefix in English." : "";
|
|
113
|
+
return `Write the commit message in ${name}. The subject line and body must be ${name}.${prefix}`;
|
|
114
|
+
}
|
|
61
115
|
function normalizeMessage(raw) {
|
|
62
116
|
let m = raw.trim();
|
|
63
117
|
if (m.startsWith("```")) {
|
|
@@ -131,10 +185,11 @@ async function runSuggest(deps, config, request) {
|
|
|
131
185
|
}
|
|
132
186
|
const pathArgs = paths.length > 0 ? ["--", ...paths] : [];
|
|
133
187
|
const pathSet = new Set(paths);
|
|
134
|
-
const [statusRes, headRes,
|
|
188
|
+
const [statusRes, headRes, logRes, emailRes] = await Promise.all([
|
|
135
189
|
runCommand(deps.run, ["git", "status", "--porcelain=v1", "-z"], root, "suggest-status", deps.signal),
|
|
136
190
|
runCommand(deps.run, ["git", "rev-parse", "--verify", "HEAD"], root, "suggest-head", deps.signal),
|
|
137
|
-
runCommand(deps.run, ["git", "log", "-
|
|
191
|
+
runCommand(deps.run, ["git", "log", `-n${HISTORY_SCAN}`, "-z", "--format=%ae%s"], root, "suggest-log", deps.signal),
|
|
192
|
+
runCommand(deps.run, ["git", "config", "user.email"], root, "suggest-email", deps.signal)
|
|
138
193
|
]);
|
|
139
194
|
if ("failure" in statusRes) return { ok: false, error: { code: "git-unavailable", message: "status failed" } };
|
|
140
195
|
if (statusRes.run.cancelled) return { ok: false, error: { code: "cancelled" } };
|
|
@@ -143,7 +198,8 @@ async function runSuggest(deps, config, request) {
|
|
|
143
198
|
return { ok: false, error: { code: "git-error", message: statusRes.run.stderr.trim() || "git status failed" } };
|
|
144
199
|
}
|
|
145
200
|
const hasHead = "run" in headRes && headRes.run.exitCode === 0;
|
|
146
|
-
const
|
|
201
|
+
const styleSamples = "run" in logRes && logRes.run.exitCode === 0 ? parseStyleHistory(logRes.run.stdout, "run" in emailRes && emailRes.run.exitCode === 0 ? emailRes.run.stdout.trim() : void 0) : [];
|
|
202
|
+
const language = resolveSuggestLanguage(config.suggestLanguage, styleSamples);
|
|
147
203
|
const all = statusRes.run.stdoutLossy ? [] : parseStatus(statusRes.run.stdout);
|
|
148
204
|
const files = pathSet.size > 0 ? all.filter((c) => pathSet.has(c.path)) : all;
|
|
149
205
|
const collected = await collectDiff(deps, root, hasHead, pathArgs);
|
|
@@ -168,17 +224,20 @@ async function runSuggest(deps, config, request) {
|
|
|
168
224
|
const frame = frameChanges(
|
|
169
225
|
files.map((c) => ({ path: c.path, status: c.status })),
|
|
170
226
|
diff,
|
|
171
|
-
|
|
227
|
+
styleSamples
|
|
172
228
|
);
|
|
173
229
|
const deadline = withDeadline(config.suggestTimeoutMs, deps.signal);
|
|
174
230
|
const options = {
|
|
175
231
|
provider: route.provider,
|
|
176
232
|
model: route.model,
|
|
177
|
-
system:
|
|
233
|
+
system: systemPrompt(language),
|
|
178
234
|
messages: [{
|
|
179
235
|
id: "git-panel-suggest",
|
|
180
236
|
role: "user",
|
|
181
|
-
content: [
|
|
237
|
+
content: [
|
|
238
|
+
{ type: "text", text: frame },
|
|
239
|
+
{ type: "text", text: languageReminder(language) }
|
|
240
|
+
],
|
|
182
241
|
source: { kind: "user" }
|
|
183
242
|
}],
|
|
184
243
|
maxTokens: config.suggestMaxOutputTokens,
|
|
@@ -216,5 +275,8 @@ async function runSuggest(deps, config, request) {
|
|
|
216
275
|
};
|
|
217
276
|
}
|
|
218
277
|
export {
|
|
278
|
+
isCjkText,
|
|
279
|
+
parseStyleHistory,
|
|
280
|
+
resolveSuggestLanguage,
|
|
219
281
|
runSuggest
|
|
220
282
|
};
|
package/lib/host/types.d.ts
CHANGED
|
@@ -372,10 +372,13 @@ export type GitQueryResult = {
|
|
|
372
372
|
* trailing gap rather than a speculative one.
|
|
373
373
|
*
|
|
374
374
|
* `truncated` marks the one case where the text itself is incomplete: the
|
|
375
|
-
* runner dropped output at its collect/spill ceiling,
|
|
376
|
-
*
|
|
377
|
-
* the
|
|
378
|
-
*
|
|
375
|
+
* runner dropped output at its collect/spill ceiling, which discards the
|
|
376
|
+
* *head* of the stream, so `text` is a tail that still parses like a real
|
|
377
|
+
* diff — the surviving hunks carry git's own line numbers — while the header
|
|
378
|
+
* and opening hunks are gone and the +/- counts under-report. It is flagged
|
|
379
|
+
* rather than refused (the partial hunks stay readable), but the view must say
|
|
380
|
+
* so: it would otherwise present the lost opening as ordinary folded context,
|
|
381
|
+
* as if the diff were whole.
|
|
379
382
|
*/
|
|
380
383
|
| {
|
|
381
384
|
readonly kind: 'diff';
|
|
@@ -517,6 +520,18 @@ export declare const SUGGEST_LIMITS: {
|
|
|
517
520
|
readonly default: 15000;
|
|
518
521
|
};
|
|
519
522
|
};
|
|
523
|
+
/**
|
|
524
|
+
* Selectable commit-message languages. `auto` infers the language from the
|
|
525
|
+
* repository's own commit history (see resolveSuggestLanguage); `zh` / `en`
|
|
526
|
+
* pin it. Shared by both halves like SUGGEST_LIMITS — the detail-page form
|
|
527
|
+
* renders one segment per entry.
|
|
528
|
+
*/
|
|
529
|
+
export declare const SUGGEST_LANGUAGES: readonly ["auto", "zh", "en"];
|
|
530
|
+
export type SuggestLanguage = (typeof SUGGEST_LANGUAGES)[number];
|
|
531
|
+
/** Default is `auto`: follow the style the repository is already written in. */
|
|
532
|
+
export declare const SUGGEST_LANGUAGE_DEFAULT: SuggestLanguage;
|
|
533
|
+
/** A language a suggestion is actually written in — `auto` is always resolved. */
|
|
534
|
+
export type ResolvedLanguage = Exclude<SuggestLanguage, 'auto'>;
|
|
520
535
|
/**
|
|
521
536
|
* Wire request for the suggest endpoint: ask the configured model to write a
|
|
522
537
|
* commit message from the repository's uncommitted changes. `paths` restricts
|
package/lib/host/types.js
CHANGED
|
@@ -7,6 +7,8 @@ const SUGGEST_LIMITS = {
|
|
|
7
7
|
maxOutputTokens: { min: 256, max: 16384, default: 4096 },
|
|
8
8
|
timeoutMs: { min: 5e3, max: 12e4, default: 15e3 }
|
|
9
9
|
};
|
|
10
|
+
const SUGGEST_LANGUAGES = ["auto", "zh", "en"];
|
|
11
|
+
const SUGGEST_LANGUAGE_DEFAULT = "auto";
|
|
10
12
|
const IMAGE_MIME = {
|
|
11
13
|
png: "image/png",
|
|
12
14
|
jpg: "image/jpeg",
|
|
@@ -31,6 +33,8 @@ export {
|
|
|
31
33
|
DIFF_COUNT_READ_MAX,
|
|
32
34
|
DIFF_EXPAND_CONTEXT,
|
|
33
35
|
IMAGE_MIME,
|
|
36
|
+
SUGGEST_LANGUAGES,
|
|
37
|
+
SUGGEST_LANGUAGE_DEFAULT,
|
|
34
38
|
SUGGEST_LIMITS,
|
|
35
39
|
imageMimeFor
|
|
36
40
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbzbing/dsh-git-panel",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "DeepSeek Harness (dsh) plugin: an IDE-style Git panel in the Web GUI — branch/history overview, uncommitted changes with commit & amend, diff stats, and a zsh-style branch marker in the input bar.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|