@sentry/warden 0.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/.agents/skills/find-bugs/SKILL.md +75 -0
- package/.agents/skills/vercel-react-best-practices/AGENTS.md +2934 -0
- package/.agents/skills/vercel-react-best-practices/SKILL.md +136 -0
- package/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/.claude/settings.json +57 -0
- package/.claude/settings.local.json +88 -0
- package/.claude/skills/agent-prompt/SKILL.md +54 -0
- package/.claude/skills/agent-prompt/references/agentic-patterns.md +94 -0
- package/.claude/skills/agent-prompt/references/anti-patterns.md +140 -0
- package/.claude/skills/agent-prompt/references/context-design.md +124 -0
- package/.claude/skills/agent-prompt/references/core-principles.md +75 -0
- package/.claude/skills/agent-prompt/references/model-guidance.md +118 -0
- package/.claude/skills/agent-prompt/references/output-formats.md +98 -0
- package/.claude/skills/agent-prompt/references/skill-structure.md +115 -0
- package/.claude/skills/agent-prompt/references/system-prompts.md +115 -0
- package/.claude/skills/notseer/SKILL.md +131 -0
- package/.claude/skills/skill-writer/SKILL.md +140 -0
- package/.claude/skills/testing-guidelines/SKILL.md +132 -0
- package/.claude/skills/warden-skill/SKILL.md +250 -0
- package/.claude/skills/warden-skill/references/config-schema.md +133 -0
- package/.dex/config.toml +2 -0
- package/.github/workflows/ci.yml +33 -0
- package/.github/workflows/release.yml +54 -0
- package/.github/workflows/warden.yml +40 -0
- package/AGENTS.md +89 -0
- package/CONTRIBUTING.md +60 -0
- package/LICENSE +105 -0
- package/README.md +43 -0
- package/SPEC.md +263 -0
- package/action.yml +87 -0
- package/assets/favicon.png +0 -0
- package/assets/warden-icon-bw.svg +5 -0
- package/assets/warden-icon-purple.png +0 -0
- package/assets/warden-icon-purple.svg +5 -0
- package/docs/.claude/settings.local.json +11 -0
- package/docs/astro.config.mjs +43 -0
- package/docs/package.json +19 -0
- package/docs/pnpm-lock.yaml +4000 -0
- package/docs/public/favicon.svg +5 -0
- package/docs/src/components/Code.astro +141 -0
- package/docs/src/components/PackageManagerTabs.astro +183 -0
- package/docs/src/components/Terminal.astro +212 -0
- package/docs/src/layouts/Base.astro +380 -0
- package/docs/src/pages/cli.astro +167 -0
- package/docs/src/pages/config.astro +394 -0
- package/docs/src/pages/guide.astro +449 -0
- package/docs/src/pages/index.astro +490 -0
- package/docs/src/styles/global.css +551 -0
- package/docs/tsconfig.json +3 -0
- package/docs/vercel.json +5 -0
- package/eslint.config.js +33 -0
- package/package.json +73 -0
- package/src/action/index.ts +1 -0
- package/src/action/main.ts +868 -0
- package/src/cli/args.test.ts +477 -0
- package/src/cli/args.ts +415 -0
- package/src/cli/commands/add.ts +447 -0
- package/src/cli/commands/init.test.ts +136 -0
- package/src/cli/commands/init.ts +132 -0
- package/src/cli/commands/setup-app/browser.ts +38 -0
- package/src/cli/commands/setup-app/credentials.ts +45 -0
- package/src/cli/commands/setup-app/manifest.ts +48 -0
- package/src/cli/commands/setup-app/server.ts +172 -0
- package/src/cli/commands/setup-app.ts +156 -0
- package/src/cli/commands/sync.ts +114 -0
- package/src/cli/context.ts +131 -0
- package/src/cli/files.test.ts +155 -0
- package/src/cli/files.ts +89 -0
- package/src/cli/fix.test.ts +310 -0
- package/src/cli/fix.ts +387 -0
- package/src/cli/git.test.ts +119 -0
- package/src/cli/git.ts +318 -0
- package/src/cli/index.ts +14 -0
- package/src/cli/main.ts +672 -0
- package/src/cli/output/box.ts +235 -0
- package/src/cli/output/formatters.test.ts +187 -0
- package/src/cli/output/formatters.ts +269 -0
- package/src/cli/output/icons.ts +13 -0
- package/src/cli/output/index.ts +44 -0
- package/src/cli/output/ink-runner.tsx +337 -0
- package/src/cli/output/jsonl.test.ts +347 -0
- package/src/cli/output/jsonl.ts +126 -0
- package/src/cli/output/reporter.ts +435 -0
- package/src/cli/output/tasks.ts +374 -0
- package/src/cli/output/tty.test.ts +117 -0
- package/src/cli/output/tty.ts +60 -0
- package/src/cli/output/verbosity.test.ts +40 -0
- package/src/cli/output/verbosity.ts +31 -0
- package/src/cli/terminal.test.ts +148 -0
- package/src/cli/terminal.ts +301 -0
- package/src/config/index.ts +3 -0
- package/src/config/loader.test.ts +313 -0
- package/src/config/loader.ts +103 -0
- package/src/config/schema.ts +168 -0
- package/src/config/writer.test.ts +119 -0
- package/src/config/writer.ts +84 -0
- package/src/diff/classify.test.ts +162 -0
- package/src/diff/classify.ts +92 -0
- package/src/diff/coalesce.test.ts +208 -0
- package/src/diff/coalesce.ts +133 -0
- package/src/diff/context.test.ts +226 -0
- package/src/diff/context.ts +201 -0
- package/src/diff/index.ts +4 -0
- package/src/diff/parser.test.ts +212 -0
- package/src/diff/parser.ts +149 -0
- package/src/event/context.ts +132 -0
- package/src/event/index.ts +2 -0
- package/src/event/schedule-context.ts +101 -0
- package/src/examples/examples.integration.test.ts +66 -0
- package/src/examples/index.test.ts +101 -0
- package/src/examples/index.ts +122 -0
- package/src/examples/setup.ts +25 -0
- package/src/index.ts +115 -0
- package/src/output/dedup.test.ts +419 -0
- package/src/output/dedup.ts +607 -0
- package/src/output/github-checks.test.ts +300 -0
- package/src/output/github-checks.ts +476 -0
- package/src/output/github-issues.ts +329 -0
- package/src/output/index.ts +5 -0
- package/src/output/issue-renderer.ts +197 -0
- package/src/output/renderer.test.ts +727 -0
- package/src/output/renderer.ts +217 -0
- package/src/output/stale.test.ts +375 -0
- package/src/output/stale.ts +155 -0
- package/src/output/types.ts +34 -0
- package/src/sdk/index.ts +1 -0
- package/src/sdk/runner.test.ts +806 -0
- package/src/sdk/runner.ts +1232 -0
- package/src/skills/index.ts +36 -0
- package/src/skills/loader.test.ts +300 -0
- package/src/skills/loader.ts +423 -0
- package/src/skills/remote.test.ts +704 -0
- package/src/skills/remote.ts +604 -0
- package/src/triggers/matcher.test.ts +277 -0
- package/src/triggers/matcher.ts +152 -0
- package/src/types/index.ts +194 -0
- package/src/utils/async.ts +18 -0
- package/src/utils/index.test.ts +84 -0
- package/src/utils/index.ts +50 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +8 -0
- package/vitest.integration.config.ts +11 -0
- package/warden.toml +19 -0
package/src/cli/git.ts
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { countPatchChunks } from '../types/index.js';
|
|
3
|
+
|
|
4
|
+
export interface GitFileChange {
|
|
5
|
+
filename: string;
|
|
6
|
+
status: 'added' | 'removed' | 'modified' | 'renamed' | 'copied';
|
|
7
|
+
additions: number;
|
|
8
|
+
deletions: number;
|
|
9
|
+
patch?: string;
|
|
10
|
+
chunks?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Execute a git command and return stdout.
|
|
15
|
+
*/
|
|
16
|
+
function git(args: string, cwd: string = process.cwd()): string {
|
|
17
|
+
try {
|
|
18
|
+
return execSync(`git ${args}`, {
|
|
19
|
+
cwd,
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
22
|
+
}).trim();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const execError = error as { stderr?: Buffer | string; message: string };
|
|
25
|
+
const stderr = execError.stderr?.toString() ?? '';
|
|
26
|
+
throw new Error(`Git command failed: git ${args}\n${stderr || execError.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the current branch name.
|
|
32
|
+
*/
|
|
33
|
+
export function getCurrentBranch(cwd: string = process.cwd()): string {
|
|
34
|
+
return git('rev-parse --abbrev-ref HEAD', cwd);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the HEAD commit SHA.
|
|
39
|
+
*/
|
|
40
|
+
export function getHeadSha(cwd: string = process.cwd()): string {
|
|
41
|
+
return git('rev-parse HEAD', cwd);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Detect the default branch by checking common branch names locally.
|
|
46
|
+
* Does not perform any remote operations to avoid SSH prompts.
|
|
47
|
+
*/
|
|
48
|
+
export function getDefaultBranch(cwd: string = process.cwd()): string {
|
|
49
|
+
// Check common default branches locally (no remote operations)
|
|
50
|
+
for (const branch of ['main', 'master', 'develop']) {
|
|
51
|
+
try {
|
|
52
|
+
git(`rev-parse --verify ${branch}`, cwd);
|
|
53
|
+
return branch;
|
|
54
|
+
} catch {
|
|
55
|
+
// Try next branch
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check git config for user-configured default branch
|
|
60
|
+
try {
|
|
61
|
+
const configuredDefault = git('config init.defaultBranch', cwd);
|
|
62
|
+
if (configuredDefault) {
|
|
63
|
+
return configuredDefault;
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Config not set
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return 'main'; // Default fallback
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the repository root path.
|
|
74
|
+
*/
|
|
75
|
+
export function getRepoRoot(cwd: string = process.cwd()): string {
|
|
76
|
+
return git('rev-parse --show-toplevel', cwd);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the repository name from the git remote or directory name.
|
|
81
|
+
*/
|
|
82
|
+
export function getRepoName(cwd: string = process.cwd()): { owner: string; name: string } {
|
|
83
|
+
try {
|
|
84
|
+
const remoteUrl = git('config --get remote.origin.url', cwd);
|
|
85
|
+
// Handle SSH: git@github.com:owner/repo.git
|
|
86
|
+
// Handle HTTPS: https://github.com/owner/repo.git
|
|
87
|
+
const match = remoteUrl.match(/[/:]([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);
|
|
88
|
+
if (match && match[1] && match[2]) {
|
|
89
|
+
return { owner: match[1], name: match[2] };
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
// No remote configured
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fall back to directory name
|
|
96
|
+
const repoRoot = getRepoRoot(cwd);
|
|
97
|
+
const dirName = repoRoot.split('/').pop() ?? 'unknown';
|
|
98
|
+
return { owner: 'local', name: dirName };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the GitHub repository URL if the remote is on GitHub.
|
|
103
|
+
* Returns null if the remote is not GitHub or not configured.
|
|
104
|
+
*/
|
|
105
|
+
export function getGitHubRepoUrl(cwd: string = process.cwd()): string | null {
|
|
106
|
+
try {
|
|
107
|
+
const remoteUrl = git('config --get remote.origin.url', cwd);
|
|
108
|
+
// Handle SSH: git@github.com:owner/repo.git
|
|
109
|
+
const sshMatch = remoteUrl.match(/git@github\.com:([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);
|
|
110
|
+
if (sshMatch && sshMatch[1] && sshMatch[2]) {
|
|
111
|
+
return `https://github.com/${sshMatch[1]}/${sshMatch[2]}`;
|
|
112
|
+
}
|
|
113
|
+
// Handle HTTPS: https://github.com/owner/repo.git
|
|
114
|
+
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);
|
|
115
|
+
if (httpsMatch && httpsMatch[1] && httpsMatch[2]) {
|
|
116
|
+
return `https://github.com/${httpsMatch[1]}/${httpsMatch[2]}`;
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// No remote configured
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Map git status letter to FileChange status.
|
|
126
|
+
*/
|
|
127
|
+
function mapStatus(status: string): GitFileChange['status'] {
|
|
128
|
+
switch (status[0]) {
|
|
129
|
+
case 'A':
|
|
130
|
+
return 'added';
|
|
131
|
+
case 'D':
|
|
132
|
+
return 'removed';
|
|
133
|
+
case 'M':
|
|
134
|
+
return 'modified';
|
|
135
|
+
case 'R':
|
|
136
|
+
return 'renamed';
|
|
137
|
+
case 'C':
|
|
138
|
+
return 'copied';
|
|
139
|
+
default:
|
|
140
|
+
return 'modified';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get list of changed files between two refs.
|
|
146
|
+
* If head is undefined, compares against the working tree.
|
|
147
|
+
*/
|
|
148
|
+
export function getChangedFiles(
|
|
149
|
+
base: string,
|
|
150
|
+
head?: string,
|
|
151
|
+
cwd: string = process.cwd()
|
|
152
|
+
): GitFileChange[] {
|
|
153
|
+
// Get file statuses
|
|
154
|
+
const diffArgs = head ? `${base}...${head}` : base;
|
|
155
|
+
const nameStatusOutput = git(`diff --name-status ${diffArgs}`, cwd);
|
|
156
|
+
|
|
157
|
+
if (!nameStatusOutput) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const files: GitFileChange[] = [];
|
|
162
|
+
|
|
163
|
+
for (const line of nameStatusOutput.split('\n')) {
|
|
164
|
+
if (!line.trim()) continue;
|
|
165
|
+
|
|
166
|
+
const parts = line.split('\t');
|
|
167
|
+
const status = parts[0] ?? '';
|
|
168
|
+
// For renames, format is "R100\told-name\tnew-name"
|
|
169
|
+
const filename = parts.length > 2 ? (parts[2] ?? '') : (parts[1] ?? '');
|
|
170
|
+
if (!filename) continue;
|
|
171
|
+
|
|
172
|
+
files.push({
|
|
173
|
+
filename,
|
|
174
|
+
status: mapStatus(status),
|
|
175
|
+
additions: 0,
|
|
176
|
+
deletions: 0,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Get numstat for additions/deletions
|
|
181
|
+
const numstatOutput = git(`diff --numstat ${diffArgs}`, cwd);
|
|
182
|
+
if (numstatOutput) {
|
|
183
|
+
for (const line of numstatOutput.split('\n')) {
|
|
184
|
+
if (!line.trim()) continue;
|
|
185
|
+
const parts = line.split('\t');
|
|
186
|
+
const additions = parts[0] ?? '0';
|
|
187
|
+
const deletions = parts[1] ?? '0';
|
|
188
|
+
const filename = parts[2] ?? '';
|
|
189
|
+
const file = files.find((f) => f.filename === filename);
|
|
190
|
+
if (file) {
|
|
191
|
+
file.additions = additions === '-' ? 0 : parseInt(additions, 10);
|
|
192
|
+
file.deletions = deletions === '-' ? 0 : parseInt(deletions, 10);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return files;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get the patch for a specific file.
|
|
202
|
+
*/
|
|
203
|
+
export function getFilePatch(
|
|
204
|
+
base: string,
|
|
205
|
+
head: string | undefined,
|
|
206
|
+
filename: string,
|
|
207
|
+
cwd: string = process.cwd()
|
|
208
|
+
): string | undefined {
|
|
209
|
+
try {
|
|
210
|
+
const diffArgs = head ? `${base}...${head}` : base;
|
|
211
|
+
return git(`diff ${diffArgs} -- "${filename}"`, cwd);
|
|
212
|
+
} catch {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Parse a combined diff output into individual file patches.
|
|
219
|
+
*/
|
|
220
|
+
function parseCombinedDiff(diffOutput: string): Map<string, string> {
|
|
221
|
+
const patches = new Map<string, string>();
|
|
222
|
+
if (!diffOutput) return patches;
|
|
223
|
+
|
|
224
|
+
// Split by "diff --git" but keep the delimiter
|
|
225
|
+
const parts = diffOutput.split(/(?=^diff --git )/m);
|
|
226
|
+
|
|
227
|
+
for (const part of parts) {
|
|
228
|
+
if (!part.trim()) continue;
|
|
229
|
+
|
|
230
|
+
// Extract filename from "diff --git a/path b/path" line
|
|
231
|
+
const match = part.match(/^diff --git a\/(.+?) b\/(.+?)\n/);
|
|
232
|
+
if (match) {
|
|
233
|
+
// Use the "b" path (destination) as the filename
|
|
234
|
+
const filename = match[2];
|
|
235
|
+
if (filename) {
|
|
236
|
+
patches.set(filename, part);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return patches;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get patches for all changed files in a single git command.
|
|
246
|
+
*/
|
|
247
|
+
export function getChangedFilesWithPatches(
|
|
248
|
+
base: string,
|
|
249
|
+
head?: string,
|
|
250
|
+
cwd: string = process.cwd()
|
|
251
|
+
): GitFileChange[] {
|
|
252
|
+
const files = getChangedFiles(base, head, cwd);
|
|
253
|
+
|
|
254
|
+
if (files.length === 0) {
|
|
255
|
+
return files;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Get all patches in a single git diff command
|
|
259
|
+
try {
|
|
260
|
+
const diffArgs = head ? `${base}...${head}` : base;
|
|
261
|
+
const combinedDiff = git(`diff ${diffArgs}`, cwd);
|
|
262
|
+
const patches = parseCombinedDiff(combinedDiff);
|
|
263
|
+
|
|
264
|
+
for (const file of files) {
|
|
265
|
+
file.patch = patches.get(file.filename);
|
|
266
|
+
file.chunks = countPatchChunks(file.patch);
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
// Fall back to per-file patches if combined diff fails
|
|
270
|
+
for (const file of files) {
|
|
271
|
+
file.patch = getFilePatch(base, head, file.filename, cwd);
|
|
272
|
+
file.chunks = countPatchChunks(file.patch);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return files;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Check if there are uncommitted changes in the working tree.
|
|
281
|
+
*/
|
|
282
|
+
export function hasUncommittedChanges(cwd: string = process.cwd()): boolean {
|
|
283
|
+
const status = git('status --porcelain', cwd);
|
|
284
|
+
return status.length > 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check if a ref exists.
|
|
289
|
+
*/
|
|
290
|
+
export function refExists(ref: string, cwd: string = process.cwd()): boolean {
|
|
291
|
+
try {
|
|
292
|
+
git(`rev-parse --verify ${ref}`, cwd);
|
|
293
|
+
return true;
|
|
294
|
+
} catch {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Commit message with subject and body separated.
|
|
301
|
+
*/
|
|
302
|
+
export interface CommitMessage {
|
|
303
|
+
/** First line of the commit message */
|
|
304
|
+
subject: string;
|
|
305
|
+
/** Remaining lines after the subject (may be empty) */
|
|
306
|
+
body: string;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get the commit message for a specific ref.
|
|
311
|
+
* Returns subject (first line) and body (remaining lines) separately.
|
|
312
|
+
*/
|
|
313
|
+
export function getCommitMessage(ref: string, cwd: string = process.cwd()): CommitMessage {
|
|
314
|
+
// %s = subject, %b = body
|
|
315
|
+
const subject = git(`log -1 --format=%s ${ref}`, cwd);
|
|
316
|
+
const body = git(`log -1 --format=%b ${ref}`, cwd);
|
|
317
|
+
return { subject, body };
|
|
318
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { main, abortController } from './main.js';
|
|
3
|
+
|
|
4
|
+
process.on('SIGINT', () => {
|
|
5
|
+
// Abort any running SDK queries
|
|
6
|
+
abortController.abort();
|
|
7
|
+
process.stderr.write('\n');
|
|
8
|
+
process.exit(130);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
main().catch((error) => {
|
|
12
|
+
console.error('Fatal error:', error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|