-
Notifications
You must be signed in to change notification settings - Fork 106
git-commit-story: add init and Copilot prompt hook #3098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Dominic Nguyen (datduyng)
merged 7 commits into
main
from
domnguyen/git-commit-story-init
Sep 29, 2026
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
d6fc0d3
Add git-commit-story init and Copilot prompt hook
datduyng ed30258
Keep existing settings keys in init and sort output
datduyng 031e511
Move git-commit-story commands into commands/
datduyng 127b3a3
docs: regenerate README.AUTOGEN.md, command reference, and action bro…
typeagent-bot[bot] f85ded5
git-commit-story: handle invalid settings JSON, fix exclude path from…
datduyng 8962cd4
git-commit-story: replace any with typed settings shape (lint ratchet)
datduyng 5bf272c
Merge remote-tracking branch 'origin/main' into domnguyen/git-commit-…
datduyng File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| import { Command } from "commander"; | ||
|
|
||
| // `hooks`: agent hook handlers. Placeholder output until story capture exists. | ||
| export const hooksCommand = new Command("hooks").description( | ||
| "Agent hook handlers", | ||
| ); | ||
|
|
||
| hooksCommand | ||
| .command("copilot") | ||
| .description("Copilot CLI hook handlers") | ||
| .command("user-prompt-submitted") | ||
| .description("Handle the Copilot userPromptSubmitted hook") | ||
| .action(() => { | ||
| process.stdout.write("Hello World\n"); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| import { Command } from "commander"; | ||
| import { execFileSync } from "node:child_process"; | ||
| import fs from "node:fs"; | ||
| import path from "node:path"; | ||
|
|
||
| // Copilot CLI reads repo-level hooks from this file. The `.local` variant is | ||
| // per-clone, so init also adds it to `.git/info/exclude`. | ||
| const COPILOT_SETTINGS = ".github/copilot/settings.local.json"; | ||
| const PROMPT_HOOK = "git commit-story hooks copilot user-prompt-submitted"; | ||
|
|
||
| // Recursively sorts object keys so the settings file has a stable order. | ||
| // Array order is kept. Example: {b:1,a:{d:2,c:3}} -> {a:{c:3,d:2},b:1}. | ||
| function sortKeys(value: unknown): unknown { | ||
| if (Array.isArray(value)) return value.map(sortKeys); | ||
| if (value === null || typeof value !== "object") return value; | ||
| return Object.fromEntries( | ||
| Object.keys(value) | ||
| .sort() | ||
| .map((k) => [k, sortKeys((value as Record<string, unknown>)[k])]), | ||
| ); | ||
| } | ||
|
|
||
| // `init`: registers agent hooks for the current repository. | ||
| export const initCommand = new Command("init") | ||
| .description("Register agent hooks for the current repository") | ||
| .action(() => { | ||
| const git = (...args: string[]) => | ||
| execFileSync("git", args, { encoding: "utf8" }).trim(); | ||
| const root = git("rev-parse", "--show-toplevel"); | ||
| const settingsPath = path.join(root, COPILOT_SETTINGS); | ||
| let settings: { | ||
| hooks?: { userPromptSubmitted?: { bash?: string }[] }; | ||
| [key: string]: unknown; | ||
| } = {}; | ||
| if (fs.existsSync(settingsPath)) { | ||
| try { | ||
| settings = JSON.parse(fs.readFileSync(settingsPath, "utf8")); | ||
| } catch (e) { | ||
| process.stderr.write( | ||
| `Failed to parse ${settingsPath}: ${(e as Error).message}\n`, | ||
| ); | ||
| process.exitCode = 1; | ||
| return; | ||
| } | ||
| } | ||
| // Update only our hook entry; keep other keys and hooks as they are. | ||
| const hook = { | ||
| type: "command", | ||
| bash: PROMPT_HOOK, | ||
| powershell: PROMPT_HOOK, | ||
| }; | ||
| settings.hooks ??= {}; | ||
| settings.hooks.userPromptSubmitted = [ | ||
| ...(settings.hooks.userPromptSubmitted ?? []).filter( | ||
| (h) => h.bash !== PROMPT_HOOK, | ||
| ), | ||
| hook, | ||
| ]; | ||
| fs.mkdirSync(path.dirname(settingsPath), { recursive: true }); | ||
| fs.writeFileSync( | ||
| settingsPath, | ||
| JSON.stringify(sortKeys(settings), null, 4) + "\n", | ||
| ); | ||
|
|
||
| // `--git-path` is relative to the cwd (not the repo root), so resolve | ||
| // it against the cwd to work from subdirectories. | ||
| const exclude = path.resolve( | ||
| git("rev-parse", "--git-path", "info/exclude"), | ||
| ); | ||
| const lines = fs.existsSync(exclude) | ||
| ? fs.readFileSync(exclude, "utf8").split("\n") | ||
| : []; | ||
| if (!lines.includes(COPILOT_SETTINGS)) { | ||
| fs.mkdirSync(path.dirname(exclude), { recursive: true }); | ||
| fs.appendFileSync(exclude, `${COPILOT_SETTINGS}\n`); | ||
| } | ||
| process.stdout.write(`Registered Copilot hooks in ${settingsPath}\n`); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.