MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / execute

Method execute

src/tools/git/release-notes.ts:55–142  ·  view source on GitHub ↗
(args: Args, ctx: ToolContext)

Source from the content-addressed store, hash-verified

53 argsSchema = ReleaseNotesArgs;
54
55 async execute(args: Args, ctx: ToolContext): Promise<ToolResult> {
56 if (!await isGitRepo(ctx.cwd, ctx.signal)) {
57 return { content: '[NOT_A_GIT_REPO] Current directory is not a git working tree.', isError: true };
58 }
59
60 const to = args.to ?? 'HEAD';
61 const scope = args.scope ?? 'user';
62 const format = args.format ?? 'markdown';
63 const maxCommits = args.max_commits ?? 500;
64
65 // Resolve `from`. If omitted, find latest tag reachable from `to`; else fall back to repo root.
66 let from = args.from;
67 let fromSource = 'user-provided';
68 if (!from) {
69 const r = await git(['describe', '--tags', '--abbrev=0', to], { cwd: ctx.cwd, signal: ctx.signal });
70 if (r.exitCode === 0 && r.stdout.trim()) {
71 from = r.stdout.trim();
72 fromSource = `latest tag: ${from}`;
73 } else {
74 // No tag — use root commit
75 try {
76 const root = (await gitOrThrow(['rev-list', '--max-parents=0', to], { cwd: ctx.cwd, signal: ctx.signal })).trim().split('\n')[0];
77 if (!root) {
78 return { content: '[EMPTY_REPO] Could not find any commits.', isError: true };
79 }
80 from = root;
81 fromSource = `repo root (no tags found)`;
82 } catch (e: any) {
83 return { content: `[ERROR] Could not resolve starting commit: ${e.message}`, isError: true };
84 }
85 }
86 }
87
88 // Build the range expression. `from..to` excludes `from` itself — desired behaviour
89 // when `from` is a tag (we don't want to re-list its commits).
90 const range = `${from}..${to}`;
91
92 const commits = await readCommits(ctx.cwd, range, maxCommits, ctx.signal);
93 if (commits.length === 0) {
94 return {
95 content: `[NO_CHANGES] No commits in range ${range} (${fromSource}).`,
96 metadata: { range, fromSource, count: 0 },
97 };
98 }
99
100 const buckets = bucket(commits);
101 const heading = args.heading ?? deriveHeading(to);
102 const md = formatMarkdown(buckets, { scope, heading, range });
103
104 let written: string[] = [];
105 if (args.write_to_changelog) {
106 const changelogPath = path.join(ctx.cwd, 'CHANGELOG.md');
107 const existing = await readIfExists(changelogPath);
108 const header = existing.startsWith('# ') ? '' : '# Changelog\n\n';
109 const next = header + md + (existing ? '\n' + existing.replace(/^# Changelog\s*\n+/, '') : '');
110 await fs.writeFile(changelogPath, next, 'utf-8');
111 written.push('CHANGELOG.md');
112 ctx.emit({ type: 'diff', path: changelogPath, before: existing || null, after: next });

Callers

nothing calls this directly

Calls 12

isGitRepoFunction · 0.85
gitOrThrowFunction · 0.85
readCommitsFunction · 0.85
bucketFunction · 0.85
deriveHeadingFunction · 0.85
formatMarkdownFunction · 0.85
readIfExistsFunction · 0.85
bumpSemverFunction · 0.85
bucketsToJsonFunction · 0.85
gitFunction · 0.70
pushMethod · 0.45
emitMethod · 0.45

Tested by

no test coverage detected