@oh-my-pi/pi-coding-agent 12.18.3 → 12.19.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/CHANGELOG.md +53 -0
- package/package.json +35 -27
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +341 -0
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +3 -17
- package/src/cli/stats-cli.ts +3 -22
- package/src/cli/web-search-cli.ts +8 -16
- package/src/commit/agentic/agent.ts +6 -9
- package/src/commit/agentic/index.ts +44 -50
- package/src/commit/agentic/state.ts +0 -9
- package/src/commit/agentic/tools/propose-commit.ts +1 -30
- package/src/commit/agentic/tools/schemas.ts +31 -0
- package/src/commit/agentic/tools/split-commit.ts +1 -30
- package/src/commit/agentic/validation.ts +1 -18
- package/src/commit/analysis/conventional.ts +3 -50
- package/src/commit/analysis/summary.ts +2 -13
- package/src/commit/changelog/detect.ts +4 -1
- package/src/commit/changelog/generate.ts +2 -25
- package/src/commit/changelog/index.ts +1 -2
- package/src/commit/cli.ts +4 -12
- package/src/commit/map-reduce/reduce-phase.ts +2 -43
- package/src/commit/pipeline.ts +7 -15
- package/src/commit/utils.ts +44 -0
- package/src/config/prompt-templates.ts +1 -81
- package/src/config/settings-schema.ts +20 -1
- package/src/config.ts +2 -3
- package/src/debug/index.ts +1 -6
- package/src/debug/system-info.ts +2 -6
- package/src/discovery/builtin.ts +5 -9
- package/src/discovery/helpers.ts +0 -26
- package/src/discovery/ssh.ts +1 -8
- package/src/exa/company.ts +8 -39
- package/src/exa/factory.ts +64 -0
- package/src/exa/index.ts +0 -16
- package/src/exa/linkedin.ts +8 -39
- package/src/exa/mcp-client.ts +0 -64
- package/src/exa/researcher.ts +17 -59
- package/src/exa/search.ts +30 -154
- package/src/extensibility/custom-tools/loader.ts +3 -41
- package/src/extensibility/extensions/loader.ts +2 -9
- package/src/extensibility/hooks/loader.ts +3 -20
- package/src/extensibility/hooks/runner.ts +3 -19
- package/src/extensibility/plugins/installer.ts +2 -1
- package/src/extensibility/plugins/loader.ts +29 -117
- package/src/extensibility/skills.ts +2 -89
- package/src/extensibility/slash-commands.ts +1 -63
- package/src/extensibility/utils.ts +38 -0
- package/src/index.ts +9 -25
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/jobs-protocol.ts +118 -0
- package/src/ipy/kernel.ts +2 -0
- package/src/lsp/config.ts +1 -5
- package/src/lsp/lspmux.ts +0 -17
- package/src/lsp/utils.ts +2 -24
- package/src/main.ts +16 -24
- package/src/mcp/client.ts +1 -46
- package/src/mcp/render.ts +8 -1
- package/src/mcp/tool-cache.ts +1 -5
- package/src/mcp/transports/http.ts +2 -7
- package/src/mcp/transports/stdio.ts +2 -7
- package/src/modes/components/bash-execution.ts +2 -16
- package/src/modes/components/extensions/inspector-panel.ts +8 -18
- package/src/modes/components/footer.ts +10 -50
- package/src/modes/components/model-selector.ts +2 -21
- package/src/modes/components/python-execution.ts +2 -16
- package/src/modes/components/settings-selector.ts +1 -10
- package/src/modes/components/status-line/segments.ts +8 -25
- package/src/modes/components/status-line.ts +14 -31
- package/src/modes/components/tool-execution.ts +8 -2
- package/src/modes/controllers/command-controller.ts +71 -30
- package/src/modes/controllers/event-controller.ts +34 -4
- package/src/modes/controllers/mcp-command-controller.ts +3 -34
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/controllers/ssh-command-controller.ts +3 -34
- package/src/modes/interactive-mode.ts +6 -2
- package/src/modes/rpc/rpc-client.ts +1 -5
- package/src/modes/shared.ts +73 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +26 -2
- package/src/patch/hashline.ts +6 -286
- package/src/patch/index.ts +6 -57
- package/src/patch/normalize.ts +22 -65
- package/src/patch/shared.ts +16 -16
- package/src/prompts/system/custom-system-prompt.md +0 -10
- package/src/prompts/system/system-prompt.md +69 -89
- package/src/prompts/tools/async-result.md +5 -0
- package/src/prompts/tools/bash.md +5 -0
- package/src/prompts/tools/cancel-job.md +7 -0
- package/src/prompts/tools/hashline.md +0 -16
- package/src/prompts/tools/poll-jobs.md +7 -0
- package/src/prompts/tools/task.md +4 -0
- package/src/sdk.ts +70 -6
- package/src/session/agent-session.ts +43 -6
- package/src/session/agent-storage.ts +69 -278
- package/src/session/auth-storage.ts +14 -1430
- package/src/session/session-manager.ts +69 -5
- package/src/session/session-storage.ts +1 -5
- package/src/session/streaming-output.ts +637 -76
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/ssh/connection-manager.ts +4 -12
- package/src/ssh/sshfs-mount.ts +3 -7
- package/src/ssh/utils.ts +8 -0
- package/src/system-prompt.ts +24 -90
- package/src/task/executor.ts +11 -1
- package/src/task/index.ts +258 -13
- package/src/task/parallel.ts +32 -0
- package/src/task/render.ts +15 -7
- package/src/task/types.ts +5 -0
- package/src/tools/ask.ts +4 -7
- package/src/tools/bash-interactive.ts +4 -5
- package/src/tools/bash.ts +125 -41
- package/src/tools/cancel-job.ts +93 -0
- package/src/tools/fetch.ts +7 -27
- package/src/tools/find.ts +3 -3
- package/src/tools/gemini-image.ts +15 -14
- package/src/tools/grep.ts +3 -3
- package/src/tools/index.ts +13 -29
- package/src/tools/json-tree.ts +12 -1
- package/src/tools/jtd-to-json-schema.ts +10 -74
- package/src/tools/jtd-to-typescript.ts +10 -72
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/notebook.ts +4 -9
- package/src/tools/output-meta.ts +52 -26
- package/src/tools/path-utils.ts +13 -7
- package/src/tools/poll-jobs.ts +178 -0
- package/src/tools/python.ts +32 -35
- package/src/tools/read.ts +61 -82
- package/src/tools/render-utils.ts +8 -159
- package/src/tools/ssh.ts +7 -20
- package/src/tools/submit-result.ts +1 -1
- package/src/tools/tool-errors.ts +0 -30
- package/src/tools/tool-result.ts +1 -2
- package/src/tools/write.ts +8 -10
- package/src/tui/code-cell.ts +8 -3
- package/src/tui/status-line.ts +4 -4
- package/src/tui/types.ts +0 -1
- package/src/tui/utils.ts +1 -14
- package/src/utils/command-args.ts +76 -0
- package/src/utils/file-mentions.ts +15 -19
- package/src/utils/frontmatter.ts +5 -10
- package/src/utils/shell-snapshot.ts +0 -11
- package/src/utils/title-generator.ts +0 -12
- package/src/web/scrapers/artifacthub.ts +7 -16
- package/src/web/scrapers/arxiv.ts +3 -8
- package/src/web/scrapers/aur.ts +8 -22
- package/src/web/scrapers/biorxiv.ts +5 -14
- package/src/web/scrapers/bluesky.ts +13 -36
- package/src/web/scrapers/brew.ts +5 -10
- package/src/web/scrapers/cheatsh.ts +2 -12
- package/src/web/scrapers/chocolatey.ts +63 -26
- package/src/web/scrapers/choosealicense.ts +3 -18
- package/src/web/scrapers/cisa-kev.ts +4 -18
- package/src/web/scrapers/clojars.ts +6 -33
- package/src/web/scrapers/coingecko.ts +25 -33
- package/src/web/scrapers/crates-io.ts +7 -26
- package/src/web/scrapers/crossref.ts +4 -18
- package/src/web/scrapers/devto.ts +11 -41
- package/src/web/scrapers/discogs.ts +7 -10
- package/src/web/scrapers/discourse.ts +6 -31
- package/src/web/scrapers/dockerhub.ts +12 -35
- package/src/web/scrapers/fdroid.ts +8 -33
- package/src/web/scrapers/firefox-addons.ts +10 -34
- package/src/web/scrapers/flathub.ts +7 -24
- package/src/web/scrapers/github-gist.ts +2 -12
- package/src/web/scrapers/github.ts +9 -47
- package/src/web/scrapers/gitlab.ts +130 -185
- package/src/web/scrapers/go-pkg.ts +12 -22
- package/src/web/scrapers/hackage.ts +88 -43
- package/src/web/scrapers/hackernews.ts +25 -45
- package/src/web/scrapers/hex.ts +19 -36
- package/src/web/scrapers/huggingface.ts +26 -91
- package/src/web/scrapers/iacr.ts +3 -8
- package/src/web/scrapers/jetbrains-marketplace.ts +9 -20
- package/src/web/scrapers/lemmy.ts +5 -23
- package/src/web/scrapers/lobsters.ts +16 -28
- package/src/web/scrapers/mastodon.ts +24 -43
- package/src/web/scrapers/maven.ts +6 -21
- package/src/web/scrapers/mdn.ts +7 -11
- package/src/web/scrapers/metacpan.ts +9 -41
- package/src/web/scrapers/musicbrainz.ts +4 -28
- package/src/web/scrapers/npm.ts +8 -25
- package/src/web/scrapers/nuget.ts +14 -37
- package/src/web/scrapers/nvd.ts +6 -28
- package/src/web/scrapers/ollama.ts +7 -34
- package/src/web/scrapers/open-vsx.ts +5 -19
- package/src/web/scrapers/opencorporates.ts +30 -14
- package/src/web/scrapers/openlibrary.ts +49 -33
- package/src/web/scrapers/orcid.ts +4 -18
- package/src/web/scrapers/osv.ts +7 -24
- package/src/web/scrapers/packagist.ts +9 -24
- package/src/web/scrapers/pub-dev.ts +7 -50
- package/src/web/scrapers/pubmed.ts +54 -21
- package/src/web/scrapers/pypi.ts +8 -26
- package/src/web/scrapers/rawg.ts +11 -19
- package/src/web/scrapers/readthedocs.ts +4 -9
- package/src/web/scrapers/reddit.ts +5 -15
- package/src/web/scrapers/repology.ts +8 -20
- package/src/web/scrapers/rfc.ts +5 -14
- package/src/web/scrapers/rubygems.ts +6 -21
- package/src/web/scrapers/searchcode.ts +8 -36
- package/src/web/scrapers/sec-edgar.ts +4 -18
- package/src/web/scrapers/semantic-scholar.ts +15 -35
- package/src/web/scrapers/snapcraft.ts +5 -19
- package/src/web/scrapers/sourcegraph.ts +5 -43
- package/src/web/scrapers/spdx.ts +4 -18
- package/src/web/scrapers/spotify.ts +4 -23
- package/src/web/scrapers/stackoverflow.ts +8 -13
- package/src/web/scrapers/terraform.ts +9 -37
- package/src/web/scrapers/tldr.ts +3 -7
- package/src/web/scrapers/twitter.ts +3 -7
- package/src/web/scrapers/types.ts +105 -27
- package/src/web/scrapers/utils.ts +97 -103
- package/src/web/scrapers/vimeo.ts +7 -27
- package/src/web/scrapers/vscode-marketplace.ts +8 -17
- package/src/web/scrapers/w3c.ts +6 -14
- package/src/web/scrapers/wikidata.ts +5 -19
- package/src/web/scrapers/wikipedia.ts +2 -12
- package/src/web/scrapers/youtube.ts +5 -34
- package/src/web/search/index.ts +0 -9
- package/src/web/search/providers/anthropic.ts +3 -2
- package/src/web/search/providers/brave.ts +3 -18
- package/src/web/search/providers/exa.ts +1 -12
- package/src/web/search/providers/kimi.ts +5 -44
- package/src/web/search/providers/perplexity.ts +1 -12
- package/src/web/search/providers/synthetic.ts +3 -26
- package/src/web/search/providers/utils.ts +36 -0
- package/src/web/search/providers/zai.ts +9 -50
- package/src/web/search/types.ts +0 -28
- package/src/web/search/utils.ts +17 -0
- package/src/tools/output-utils.ts +0 -63
- package/src/tools/truncate.ts +0 -385
- package/src/web/search/auth.ts +0 -178
|
@@ -115,7 +115,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
115
115
|
clearThinkingLine();
|
|
116
116
|
const assistantMessage = event.message as { stopReason?: string; errorMessage?: string };
|
|
117
117
|
if (assistantMessage.stopReason === "error" && assistantMessage.errorMessage) {
|
|
118
|
-
|
|
118
|
+
process.stdout.write(`● Error: ${assistantMessage.errorMessage}\n`);
|
|
119
119
|
}
|
|
120
120
|
const messageText = extractMessageText(event.message?.content ?? []);
|
|
121
121
|
if (messageText) {
|
|
@@ -130,10 +130,10 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
130
130
|
clearThinkingLine();
|
|
131
131
|
const toolLabel = formatToolLabel(stored.name);
|
|
132
132
|
const symbol = event.isError ? "" : "";
|
|
133
|
-
|
|
133
|
+
process.stdout.write(`${symbol} ${toolLabel}\n`);
|
|
134
134
|
const argsLines = formatToolArgs(stored.args);
|
|
135
135
|
if (argsLines.length > 0) {
|
|
136
|
-
|
|
136
|
+
process.stdout.write(`${formatToolArgsBlock(argsLines)}\n`);
|
|
137
137
|
}
|
|
138
138
|
break;
|
|
139
139
|
}
|
|
@@ -141,7 +141,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
141
141
|
if (isThinking) {
|
|
142
142
|
isThinking = false;
|
|
143
143
|
}
|
|
144
|
-
|
|
144
|
+
process.stdout.write(`● agent finished (${messageCount} messages, ${toolCalls} tools)\n`);
|
|
145
145
|
break;
|
|
146
146
|
default:
|
|
147
147
|
break;
|
|
@@ -172,10 +172,6 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
function writeStdout(message: string): void {
|
|
176
|
-
process.stdout.write(`${message}\n`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
175
|
function extractMessagePreview(content: Array<{ type: string; text?: string }>): string | null {
|
|
180
176
|
const textBlocks = content
|
|
181
177
|
.filter(block => block.type === "text" && typeof block.text === "string")
|
|
@@ -204,7 +200,7 @@ function writeAssistantMessage(message: string): void {
|
|
|
204
200
|
}
|
|
205
201
|
for (const [index, line] of lines.entries()) {
|
|
206
202
|
const prefix = index === firstContentIndex ? "● " : " ";
|
|
207
|
-
|
|
203
|
+
process.stdout.write(`${`${prefix}${line}`.trimEnd()}\n`);
|
|
208
204
|
}
|
|
209
205
|
}
|
|
210
206
|
|
|
@@ -249,6 +245,7 @@ function formatToolArgs(args?: Record<string, unknown>): string[] {
|
|
|
249
245
|
}
|
|
250
246
|
};
|
|
251
247
|
for (const [key, value] of Object.entries(args)) {
|
|
248
|
+
if (key === "agent__intent") continue;
|
|
252
249
|
visit(value, key);
|
|
253
250
|
}
|
|
254
251
|
return lines;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
|
-
import { $env } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { $env, isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
5
5
|
import { applyChangelogProposals } from "../../commit/changelog";
|
|
6
6
|
import { detectChangelogBoundaries } from "../../commit/changelog/detect";
|
|
@@ -31,13 +31,13 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
31
31
|
const git = new ControlledGit(cwd);
|
|
32
32
|
const [settings, authStorage] = await Promise.all([Settings.init({ cwd }), discoverAuthStorage()]);
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
process.stdout.write("● Resolving model...\n");
|
|
35
35
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
36
36
|
await modelRegistry.refresh();
|
|
37
37
|
const stagedFilesPromise = (async () => {
|
|
38
38
|
let stagedFiles = await git.getStagedFiles();
|
|
39
39
|
if (stagedFiles.length === 0) {
|
|
40
|
-
|
|
40
|
+
process.stdout.write("No staged changes detected, staging all changes...\n");
|
|
41
41
|
await git.stageAll();
|
|
42
42
|
stagedFiles = await git.getStagedFiles();
|
|
43
43
|
}
|
|
@@ -47,17 +47,17 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
47
47
|
const primaryModelPromise = resolvePrimaryModel(args.model, settings, modelRegistry);
|
|
48
48
|
const [primaryModelResult, stagedFiles] = await Promise.all([primaryModelPromise, stagedFilesPromise]);
|
|
49
49
|
const { model: primaryModel, apiKey: primaryApiKey } = primaryModelResult;
|
|
50
|
-
|
|
50
|
+
process.stdout.write(` └─ ${primaryModel.name}\n`);
|
|
51
51
|
|
|
52
52
|
const { model: agentModel } = await resolveSmolModel(settings, modelRegistry, primaryModel, primaryApiKey);
|
|
53
53
|
|
|
54
54
|
if (stagedFiles.length === 0) {
|
|
55
|
-
|
|
55
|
+
process.stderr.write("No changes to commit.\n");
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (!args.noChangelog) {
|
|
60
|
-
|
|
60
|
+
process.stdout.write("● Detecting changelog targets...\n");
|
|
61
61
|
}
|
|
62
62
|
const [changelogBoundaries, contextFiles, numstat, diff] = await Promise.all([
|
|
63
63
|
args.noChangelog ? [] : detectChangelogBoundaries(cwd, stagedFiles),
|
|
@@ -69,25 +69,25 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
69
69
|
if (!args.noChangelog) {
|
|
70
70
|
if (changelogTargets.length > 0) {
|
|
71
71
|
for (const path of changelogTargets) {
|
|
72
|
-
|
|
72
|
+
process.stdout.write(` └─ ${path}\n`);
|
|
73
73
|
}
|
|
74
74
|
} else {
|
|
75
|
-
|
|
75
|
+
process.stdout.write(" └─ (none found)\n");
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
process.stdout.write("● Discovering context files...\n");
|
|
80
80
|
const agentsMdFiles = contextFiles.filter(file => file.path.endsWith("AGENTS.md"));
|
|
81
81
|
if (agentsMdFiles.length > 0) {
|
|
82
82
|
for (const file of agentsMdFiles) {
|
|
83
|
-
|
|
83
|
+
process.stdout.write(` └─ ${file.path}\n`);
|
|
84
84
|
}
|
|
85
85
|
} else {
|
|
86
|
-
|
|
86
|
+
process.stdout.write(" └─ (none found)\n");
|
|
87
87
|
}
|
|
88
88
|
const forceFallback = $env.PI_COMMIT_TEST_FALLBACK?.toLowerCase() === "true";
|
|
89
89
|
if (forceFallback) {
|
|
90
|
-
|
|
90
|
+
process.stdout.write("● Forcing fallback commit generation...\n");
|
|
91
91
|
const fallbackProposal = generateFallbackProposal(numstat);
|
|
92
92
|
await runSingleCommit(fallbackProposal, { git, dryRun: args.dryRun, push: args.push });
|
|
93
93
|
return;
|
|
@@ -95,7 +95,7 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
95
95
|
|
|
96
96
|
const trivialChange = detectTrivialChange(diff);
|
|
97
97
|
if (trivialChange) {
|
|
98
|
-
|
|
98
|
+
process.stdout.write(`● Detected trivial change: ${trivialChange.summary}\n`);
|
|
99
99
|
const trivialProposal: CommitProposal = {
|
|
100
100
|
analysis: {
|
|
101
101
|
type: trivialChange.type,
|
|
@@ -118,7 +118,7 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
process.stdout.write("● Starting commit agent...\n");
|
|
122
122
|
let commitState: CommitAgentState;
|
|
123
123
|
let usedFallback = false;
|
|
124
124
|
|
|
@@ -139,18 +139,18 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
139
139
|
});
|
|
140
140
|
} catch (error) {
|
|
141
141
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
142
|
-
|
|
142
|
+
process.stderr.write(`Agent error: ${errorMessage}\n`);
|
|
143
143
|
if (error instanceof Error && error.stack && $env.DEBUG) {
|
|
144
|
-
|
|
144
|
+
process.stderr.write(`${error.stack}\n`);
|
|
145
145
|
}
|
|
146
|
-
|
|
146
|
+
process.stdout.write("● Using fallback commit generation...\n");
|
|
147
147
|
commitState = { proposal: generateFallbackProposal(numstat) };
|
|
148
148
|
usedFallback = true;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
if (!usedFallback && !commitState.proposal && !commitState.splitProposal) {
|
|
152
152
|
if ($env.PI_COMMIT_NO_FALLBACK?.toLowerCase() !== "true") {
|
|
153
|
-
|
|
153
|
+
process.stdout.write("● Agent did not provide proposal, using fallback...\n");
|
|
154
154
|
commitState.proposal = generateFallbackProposal(numstat);
|
|
155
155
|
usedFallback = true;
|
|
156
156
|
}
|
|
@@ -159,26 +159,26 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
159
159
|
let updatedChangelogFiles: string[] = [];
|
|
160
160
|
if (!args.noChangelog && changelogTargets.length > 0 && !usedFallback) {
|
|
161
161
|
if (!commitState.changelogProposal) {
|
|
162
|
-
|
|
162
|
+
process.stderr.write("Commit agent did not provide changelog entries.\n");
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
|
-
|
|
165
|
+
process.stdout.write("● Applying changelog entries...\n");
|
|
166
166
|
const updated = await applyChangelogProposals({
|
|
167
167
|
git,
|
|
168
168
|
cwd,
|
|
169
169
|
proposals: commitState.changelogProposal.entries,
|
|
170
170
|
dryRun: args.dryRun,
|
|
171
171
|
onProgress: message => {
|
|
172
|
-
|
|
172
|
+
process.stdout.write(` ├─ ${message}\n`);
|
|
173
173
|
},
|
|
174
174
|
});
|
|
175
175
|
updatedChangelogFiles = updated.map(filePath => path.relative(cwd, filePath));
|
|
176
176
|
if (updated.length > 0) {
|
|
177
177
|
for (const filePath of updated) {
|
|
178
|
-
|
|
178
|
+
process.stdout.write(` └─ ${filePath}\n`);
|
|
179
179
|
}
|
|
180
180
|
} else {
|
|
181
|
-
|
|
181
|
+
process.stdout.write(" └─ (no changes)\n");
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
|
|
@@ -197,24 +197,24 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
197
197
|
return;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
process.stderr.write("Commit agent did not provide a proposal.\n");
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
async function runSingleCommit(proposal: CommitProposal, ctx: CommitExecutionContext): Promise<void> {
|
|
204
204
|
if (proposal.warnings.length > 0) {
|
|
205
|
-
|
|
205
|
+
process.stdout.write(formatWarnings(proposal.warnings));
|
|
206
206
|
}
|
|
207
207
|
const commitMessage = formatCommitMessage(proposal.analysis, proposal.summary);
|
|
208
208
|
if (ctx.dryRun) {
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
process.stdout.write("\nGenerated commit message:\n");
|
|
210
|
+
process.stdout.write(`${commitMessage}\n`);
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
213
|
await ctx.git.commit(commitMessage);
|
|
214
|
-
|
|
214
|
+
process.stdout.write("Commit created.\n");
|
|
215
215
|
if (ctx.push) {
|
|
216
216
|
await ctx.git.push();
|
|
217
|
-
|
|
217
|
+
process.stdout.write("Pushed to remote.\n");
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
|
|
@@ -223,7 +223,7 @@ async function runSplitCommit(
|
|
|
223
223
|
ctx: CommitExecutionContext & { additionalFiles?: string[] },
|
|
224
224
|
): Promise<void> {
|
|
225
225
|
if (plan.warnings.length > 0) {
|
|
226
|
-
|
|
226
|
+
process.stdout.write(formatWarnings(plan.warnings));
|
|
227
227
|
}
|
|
228
228
|
if (ctx.additionalFiles && ctx.additionalFiles.length > 0) {
|
|
229
229
|
appendFilesToLastCommit(plan, ctx.additionalFiles);
|
|
@@ -232,12 +232,12 @@ async function runSplitCommit(
|
|
|
232
232
|
const plannedFiles = new Set(plan.commits.flatMap(commit => commit.changes.map(change => change.path)));
|
|
233
233
|
const missingFiles = stagedFiles.filter(file => !plannedFiles.has(file));
|
|
234
234
|
if (missingFiles.length > 0) {
|
|
235
|
-
|
|
235
|
+
process.stderr.write(`Split commit plan missing staged files: ${missingFiles.join(", ")}\n`);
|
|
236
236
|
return;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
if (ctx.dryRun) {
|
|
240
|
-
|
|
240
|
+
process.stdout.write("\nSplit commit plan (dry run):\n");
|
|
241
241
|
for (const [index, commit] of plan.commits.entries()) {
|
|
242
242
|
const analysis: ConventionalAnalysis = {
|
|
243
243
|
type: commit.type,
|
|
@@ -246,17 +246,17 @@ async function runSplitCommit(
|
|
|
246
246
|
issueRefs: commit.issueRefs,
|
|
247
247
|
};
|
|
248
248
|
const message = formatCommitMessage(analysis, commit.summary);
|
|
249
|
-
|
|
249
|
+
process.stdout.write(`Commit ${index + 1}:\n${message}\n`);
|
|
250
250
|
const changeSummary = commit.changes
|
|
251
251
|
.map(change => formatFileChangeSummary(change.path, change.hunks))
|
|
252
252
|
.join(", ");
|
|
253
|
-
|
|
253
|
+
process.stdout.write(`Changes: ${changeSummary}\n`);
|
|
254
254
|
}
|
|
255
255
|
return;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
if (!(await confirmSplitCommitPlan(plan))) {
|
|
259
|
-
|
|
259
|
+
process.stdout.write("Split commit aborted by user.\n");
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
262
|
|
|
@@ -279,10 +279,10 @@ async function runSplitCommit(
|
|
|
279
279
|
await ctx.git.commit(message);
|
|
280
280
|
await ctx.git.resetStaging();
|
|
281
281
|
}
|
|
282
|
-
|
|
282
|
+
process.stdout.write("Split commits created.\n");
|
|
283
283
|
if (ctx.push) {
|
|
284
284
|
await ctx.git.push();
|
|
285
|
-
|
|
285
|
+
process.stdout.write("Pushed to remote.\n");
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
|
|
@@ -312,15 +312,7 @@ async function confirmSplitCommitPlan(plan: SplitCommitPlan): Promise<boolean> {
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
function formatWarnings(warnings: string[]): string {
|
|
315
|
-
return `Warnings:\n${warnings.map(warning => `- ${warning}`).join("\n")}`;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function writeStdout(message: string): void {
|
|
319
|
-
process.stdout.write(`${message}\n`);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function writeStderr(message: string): void {
|
|
323
|
-
process.stderr.write(`${message}\n`);
|
|
315
|
+
return `Warnings:\n${warnings.map(warning => `- ${warning}`).join("\n")}\n`;
|
|
324
316
|
}
|
|
325
317
|
|
|
326
318
|
function formatFileChangeSummary(path: string, hunks: HunkSelector): string {
|
|
@@ -336,11 +328,13 @@ function formatFileChangeSummary(path: string, hunks: HunkSelector): string {
|
|
|
336
328
|
async function loadExistingChangelogEntries(paths: string[]): Promise<ExistingChangelogEntries[]> {
|
|
337
329
|
const entries = await Promise.all(
|
|
338
330
|
paths.map(async path => {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
331
|
+
let content: string;
|
|
332
|
+
try {
|
|
333
|
+
content = await Bun.file(path).text();
|
|
334
|
+
} catch (err) {
|
|
335
|
+
if (isEnoent(err)) return null;
|
|
336
|
+
throw err;
|
|
342
337
|
}
|
|
343
|
-
const content = await file.text();
|
|
344
338
|
try {
|
|
345
339
|
const unreleased = parseUnreleasedSection(content);
|
|
346
340
|
const sections = Object.entries(unreleased.entries)
|
|
@@ -16,15 +16,6 @@ export interface CommitProposal {
|
|
|
16
16
|
warnings: string[];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export interface FileObservation {
|
|
20
|
-
file: string;
|
|
21
|
-
summary: string;
|
|
22
|
-
highlights: string[];
|
|
23
|
-
risks: string[];
|
|
24
|
-
additions: number;
|
|
25
|
-
deletions: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
19
|
export type HunkSelector =
|
|
29
20
|
| { type: "all" }
|
|
30
21
|
| { type: "indices"; indices: number[] }
|
|
@@ -12,36 +12,7 @@ import { validateAnalysis } from "../../../commit/analysis/validation";
|
|
|
12
12
|
import type { ControlledGit } from "../../../commit/git";
|
|
13
13
|
import type { CommitType, ConventionalAnalysis, ConventionalDetail } from "../../../commit/types";
|
|
14
14
|
import type { CustomTool } from "../../../extensibility/custom-tools/types";
|
|
15
|
-
|
|
16
|
-
const commitTypeSchema = Type.Union([
|
|
17
|
-
Type.Literal("feat"),
|
|
18
|
-
Type.Literal("fix"),
|
|
19
|
-
Type.Literal("refactor"),
|
|
20
|
-
Type.Literal("perf"),
|
|
21
|
-
Type.Literal("docs"),
|
|
22
|
-
Type.Literal("test"),
|
|
23
|
-
Type.Literal("build"),
|
|
24
|
-
Type.Literal("ci"),
|
|
25
|
-
Type.Literal("chore"),
|
|
26
|
-
Type.Literal("style"),
|
|
27
|
-
Type.Literal("revert"),
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
const detailSchema = Type.Object({
|
|
31
|
-
text: Type.String(),
|
|
32
|
-
changelog_category: Type.Optional(
|
|
33
|
-
Type.Union([
|
|
34
|
-
Type.Literal("Added"),
|
|
35
|
-
Type.Literal("Changed"),
|
|
36
|
-
Type.Literal("Fixed"),
|
|
37
|
-
Type.Literal("Deprecated"),
|
|
38
|
-
Type.Literal("Removed"),
|
|
39
|
-
Type.Literal("Security"),
|
|
40
|
-
Type.Literal("Breaking Changes"),
|
|
41
|
-
]),
|
|
42
|
-
),
|
|
43
|
-
user_visible: Type.Optional(Type.Boolean()),
|
|
44
|
-
});
|
|
15
|
+
import { commitTypeSchema, detailSchema } from "./schemas.js";
|
|
45
16
|
|
|
46
17
|
const proposeCommitSchema = Type.Object({
|
|
47
18
|
type: commitTypeSchema,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
export const commitTypeSchema = Type.Union([
|
|
4
|
+
Type.Literal("feat"),
|
|
5
|
+
Type.Literal("fix"),
|
|
6
|
+
Type.Literal("refactor"),
|
|
7
|
+
Type.Literal("perf"),
|
|
8
|
+
Type.Literal("docs"),
|
|
9
|
+
Type.Literal("test"),
|
|
10
|
+
Type.Literal("build"),
|
|
11
|
+
Type.Literal("ci"),
|
|
12
|
+
Type.Literal("chore"),
|
|
13
|
+
Type.Literal("style"),
|
|
14
|
+
Type.Literal("revert"),
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export const detailSchema = Type.Object({
|
|
18
|
+
text: Type.String(),
|
|
19
|
+
changelog_category: Type.Optional(
|
|
20
|
+
Type.Union([
|
|
21
|
+
Type.Literal("Added"),
|
|
22
|
+
Type.Literal("Changed"),
|
|
23
|
+
Type.Literal("Fixed"),
|
|
24
|
+
Type.Literal("Deprecated"),
|
|
25
|
+
Type.Literal("Removed"),
|
|
26
|
+
Type.Literal("Security"),
|
|
27
|
+
Type.Literal("Breaking Changes"),
|
|
28
|
+
]),
|
|
29
|
+
),
|
|
30
|
+
user_visible: Type.Optional(Type.Boolean()),
|
|
31
|
+
});
|
|
@@ -13,36 +13,7 @@ import { validateScope } from "../../../commit/analysis/validation";
|
|
|
13
13
|
import type { ControlledGit } from "../../../commit/git";
|
|
14
14
|
import type { ConventionalDetail } from "../../../commit/types";
|
|
15
15
|
import type { CustomTool } from "../../../extensibility/custom-tools/types";
|
|
16
|
-
|
|
17
|
-
const commitTypeSchema = Type.Union([
|
|
18
|
-
Type.Literal("feat"),
|
|
19
|
-
Type.Literal("fix"),
|
|
20
|
-
Type.Literal("refactor"),
|
|
21
|
-
Type.Literal("perf"),
|
|
22
|
-
Type.Literal("docs"),
|
|
23
|
-
Type.Literal("test"),
|
|
24
|
-
Type.Literal("build"),
|
|
25
|
-
Type.Literal("ci"),
|
|
26
|
-
Type.Literal("chore"),
|
|
27
|
-
Type.Literal("style"),
|
|
28
|
-
Type.Literal("revert"),
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
const detailSchema = Type.Object({
|
|
32
|
-
text: Type.String(),
|
|
33
|
-
changelog_category: Type.Optional(
|
|
34
|
-
Type.Union([
|
|
35
|
-
Type.Literal("Added"),
|
|
36
|
-
Type.Literal("Changed"),
|
|
37
|
-
Type.Literal("Fixed"),
|
|
38
|
-
Type.Literal("Deprecated"),
|
|
39
|
-
Type.Literal("Removed"),
|
|
40
|
-
Type.Literal("Security"),
|
|
41
|
-
Type.Literal("Breaking Changes"),
|
|
42
|
-
]),
|
|
43
|
-
),
|
|
44
|
-
user_visible: Type.Optional(Type.Boolean()),
|
|
45
|
-
});
|
|
16
|
+
import { commitTypeSchema, detailSchema } from "./schemas.js";
|
|
46
17
|
|
|
47
18
|
const hunkSelectorSchema = Type.Union([
|
|
48
19
|
Type.Object({ type: Type.Literal("all") }),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { stripTypePrefix } from "../../commit/analysis/summary";
|
|
2
2
|
import { validateSummary } from "../../commit/analysis/validation";
|
|
3
3
|
import type { CommitType, ConventionalDetail } from "../../commit/types";
|
|
4
|
+
import { normalizeUnicode } from "../../patch/normalize";
|
|
4
5
|
|
|
5
6
|
export const SUMMARY_MAX_CHARS = 72;
|
|
6
7
|
export const MAX_DETAIL_ITEMS = 6;
|
|
@@ -61,29 +62,11 @@ const pastTenseVerbs = new Set([
|
|
|
61
62
|
]);
|
|
62
63
|
const pastTenseEdExceptions = new Set(["hundred", "red", "bed"]);
|
|
63
64
|
|
|
64
|
-
const unicodeReplacements: Array<[RegExp, string]> = [
|
|
65
|
-
[/[\u2018\u2019]/g, "'"],
|
|
66
|
-
[/[\u201C\u201D]/g, '"'],
|
|
67
|
-
[/[\u2013\u2014\u2212]/g, "-"],
|
|
68
|
-
[/\u2260/g, "!="],
|
|
69
|
-
[/\u00BD/g, "1/2"],
|
|
70
|
-
[/\u03BB/g, "lambda"],
|
|
71
|
-
[/[\u200B-\u200D\uFEFF]/g, ""],
|
|
72
|
-
];
|
|
73
|
-
|
|
74
65
|
export function normalizeSummary(summary: string, type: CommitType, scope: string | null): string {
|
|
75
66
|
const stripped = stripTypePrefix(summary, type, scope);
|
|
76
67
|
return normalizeUnicode(stripped).replace(/\s+/g, " ").trim();
|
|
77
68
|
}
|
|
78
69
|
|
|
79
|
-
export function normalizeUnicode(text: string): string {
|
|
80
|
-
let result = text;
|
|
81
|
-
for (const [pattern, replacement] of unicodeReplacements) {
|
|
82
|
-
result = result.replace(pattern, replacement);
|
|
83
|
-
}
|
|
84
|
-
return result.normalize("NFC");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
70
|
export function validateSummaryRules(summary: string): { errors: string[]; warnings: string[] } {
|
|
88
71
|
const errors: string[] = [];
|
|
89
72
|
const warnings: string[] = [];
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { Api, AssistantMessage, Model
|
|
1
|
+
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import analysisSystemPrompt from "../../commit/prompts/analysis-system.md" with { type: "text" };
|
|
5
5
|
import analysisUserPrompt from "../../commit/prompts/analysis-user.md" with { type: "text" };
|
|
6
|
-
import type { ChangelogCategory, ConventionalAnalysis
|
|
6
|
+
import type { ChangelogCategory, ConventionalAnalysis } from "../../commit/types";
|
|
7
7
|
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
8
|
+
import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "../utils";
|
|
8
9
|
|
|
9
10
|
const ConventionalAnalysisTool = {
|
|
10
11
|
name: "create_conventional_analysis",
|
|
@@ -115,51 +116,3 @@ function parseAnalysisFromResponse(message: AssistantMessage): ConventionalAnaly
|
|
|
115
116
|
};
|
|
116
117
|
return normalizeAnalysis(parsed);
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
-
function normalizeAnalysis(parsed: {
|
|
120
|
-
type: ConventionalAnalysis["type"];
|
|
121
|
-
scope: string | null;
|
|
122
|
-
details: Array<{ text: string; changelog_category?: ChangelogCategory; user_visible?: boolean }>;
|
|
123
|
-
issue_refs: string[];
|
|
124
|
-
}): ConventionalAnalysis {
|
|
125
|
-
const details: ConventionalDetail[] = parsed.details.map(detail => ({
|
|
126
|
-
text: detail.text.trim(),
|
|
127
|
-
changelogCategory: detail.user_visible ? detail.changelog_category : undefined,
|
|
128
|
-
userVisible: detail.user_visible ?? false,
|
|
129
|
-
}));
|
|
130
|
-
return {
|
|
131
|
-
type: parsed.type,
|
|
132
|
-
scope: parsed.scope?.trim() || null,
|
|
133
|
-
details,
|
|
134
|
-
issueRefs: parsed.issue_refs ?? [],
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function extractToolCall(message: AssistantMessage, name: string): ToolCall | undefined {
|
|
139
|
-
for (const content of message.content) {
|
|
140
|
-
if (content.type === "toolCall" && content.name === name) {
|
|
141
|
-
return content;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function extractTextContent(message: AssistantMessage): string {
|
|
148
|
-
return message.content
|
|
149
|
-
.filter(content => content.type === "text")
|
|
150
|
-
.map(content => content.text)
|
|
151
|
-
.join("")
|
|
152
|
-
.trim();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function parseJsonPayload(text: string): unknown {
|
|
156
|
-
const trimmed = text.trim();
|
|
157
|
-
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
158
|
-
return JSON.parse(trimmed) as unknown;
|
|
159
|
-
}
|
|
160
|
-
const match = trimmed.match(/\{[\s\S]*\}/);
|
|
161
|
-
if (!match) {
|
|
162
|
-
throw new Error("No JSON payload found in analysis response");
|
|
163
|
-
}
|
|
164
|
-
return JSON.parse(match[0]) as unknown;
|
|
165
|
-
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { Api, AssistantMessage, Model
|
|
1
|
+
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import summarySystemPrompt from "../../commit/prompts/summary-system.md" with { type: "text" };
|
|
5
5
|
import summaryUserPrompt from "../../commit/prompts/summary-user.md" with { type: "text" };
|
|
6
6
|
import type { CommitSummary } from "../../commit/types";
|
|
7
7
|
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
8
|
+
import { extractTextContent, extractToolCall } from "../utils";
|
|
8
9
|
|
|
9
10
|
const SummaryTool = {
|
|
10
11
|
name: "create_commit_summary",
|
|
@@ -85,18 +86,6 @@ function parseSummaryFromResponse(message: AssistantMessage, commitType: string,
|
|
|
85
86
|
return { summary: stripTypePrefix(text, commitType, scope) };
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
function extractToolCall(message: AssistantMessage, name: string): ToolCall | undefined {
|
|
89
|
-
return message.content.find(content => content.type === "toolCall" && content.name === name) as ToolCall | undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function extractTextContent(message: AssistantMessage): string {
|
|
93
|
-
return message.content
|
|
94
|
-
.filter(content => content.type === "text")
|
|
95
|
-
.map(content => content.text)
|
|
96
|
-
.join("")
|
|
97
|
-
.trim();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
89
|
export function stripTypePrefix(summary: string, commitType: string, scope: string | null): string {
|
|
101
90
|
const trimmed = summary.trim();
|
|
102
91
|
const scopePart = scope ? `(${scope})` : "";
|
|
@@ -26,8 +26,11 @@ async function findNearestChangelog(cwd: string, filePath: string): Promise<stri
|
|
|
26
26
|
const root = path.resolve(cwd);
|
|
27
27
|
while (true) {
|
|
28
28
|
const candidate = path.resolve(current, CHANGELOG_NAME);
|
|
29
|
-
|
|
29
|
+
try {
|
|
30
|
+
await fs.promises.access(candidate);
|
|
30
31
|
return candidate;
|
|
32
|
+
} catch {
|
|
33
|
+
// not found, continue traversal
|
|
31
34
|
}
|
|
32
35
|
if (current === root) return null;
|
|
33
36
|
const parent = path.dirname(current);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { Api, AssistantMessage, Model
|
|
1
|
+
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import changelogSystemPrompt from "../../commit/prompts/changelog-system.md" with { type: "text" };
|
|
5
5
|
import changelogUserPrompt from "../../commit/prompts/changelog-user.md" with { type: "text" };
|
|
6
6
|
import type { ChangelogGenerationResult } from "../../commit/types";
|
|
7
7
|
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
8
|
+
import { extractTextContent, extractToolCall, parseJsonPayload } from "../utils";
|
|
8
9
|
|
|
9
10
|
const ChangelogTool = {
|
|
10
11
|
name: "create_changelog_entries",
|
|
@@ -66,30 +67,6 @@ function parseChangelogResponse(message: AssistantMessage): ChangelogGenerationR
|
|
|
66
67
|
return { entries: parsed.entries ?? {} };
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
function extractToolCall(message: AssistantMessage, name: string): ToolCall | undefined {
|
|
70
|
-
return message.content.find(content => content.type === "toolCall" && content.name === name) as ToolCall | undefined;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function extractTextContent(message: AssistantMessage): string {
|
|
74
|
-
return message.content
|
|
75
|
-
.filter(content => content.type === "text")
|
|
76
|
-
.map(content => content.text)
|
|
77
|
-
.join("")
|
|
78
|
-
.trim();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function parseJsonPayload(text: string): unknown {
|
|
82
|
-
const trimmed = text.trim();
|
|
83
|
-
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
84
|
-
return JSON.parse(trimmed) as unknown;
|
|
85
|
-
}
|
|
86
|
-
const match = trimmed.match(/\{[\s\S]*\}/);
|
|
87
|
-
if (!match) {
|
|
88
|
-
throw new Error("No JSON payload found in changelog response");
|
|
89
|
-
}
|
|
90
|
-
return JSON.parse(match[0]) as unknown;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
70
|
function dedupeEntries(entries: Record<string, string[]>): Record<string, string[]> {
|
|
94
71
|
const result: Record<string, string[]> = {};
|
|
95
72
|
for (const [category, values] of Object.entries(entries)) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
1
|
import * as path from "node:path";
|
|
3
2
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
4
3
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
@@ -109,7 +108,7 @@ export async function applyChangelogProposals({
|
|
|
109
108
|
)
|
|
110
109
|
continue;
|
|
111
110
|
onProgress?.(`Applying entries for ${proposal.path}…`);
|
|
112
|
-
const exists =
|
|
111
|
+
const exists = await Bun.file(proposal.path).exists();
|
|
113
112
|
if (!exists) {
|
|
114
113
|
logger.warn("commit changelog path missing", { path: proposal.path });
|
|
115
114
|
continue;
|