Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "codex-comment-checker",
"version": "0.1.1",
"description": "Run comment-checker after Codex apply_patch edits.",
"description": "Run comment-checker after Codex edit hooks.",
"author": {
"name": "Yeongyu Kim",
"email": "yeongyu@users.noreply.github.com",
Expand All @@ -15,18 +15,18 @@
"hooks": "./hooks/hooks.json",
"interface": {
"displayName": "Codex Comment Checker",
"shortDescription": "Checks comments after apply_patch",
"longDescription": "Codex Comment Checker runs the native comment-checker after successful apply_patch tool calls and feeds warnings back to Codex through PostToolUse hook feedback.",
"shortDescription": "Checks comments after edits",
"longDescription": "Codex Comment Checker runs the native comment-checker after successful edit-like PostToolUse hook calls and feeds warnings back to Codex through blocking hook feedback.",
"developerName": "Yeongyu Kim",
"category": "Developer Tools",
"capabilities": ["Hooks", "Code Review"],
"websiteURL": "https://github.com/code-yeongyu/codex-comment-checker",
"privacyPolicyURL": "https://github.com/code-yeongyu/codex-comment-checker#privacy",
"termsOfServiceURL": "https://github.com/code-yeongyu/codex-comment-checker#license",
"defaultPrompt": [
"Explain the comment warning from the apply_patch hook.",
"Explain the comment warning from the edit hook.",
"Fix the comment-checker warning from my last patch.",
"Show why the apply_patch comment check blocked."
"Show why the comment check blocked."
],
"brandColor": "#2563EB",
"screenshots": []
Expand Down
13 changes: 13 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Normalize line endings: store LF in git, check out LF on every platform.
# Required so biome's --check passes on Windows (default core.autocrlf=true).
* text=auto eol=lf

# Explicit binary types
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.zip binary
*.tgz binary
*.gz binary
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
node: ["20", "22"]
steps:
- name: Checkout
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## Unreleased

### Added

- Restore `write`, `edit`, `multi_edit`, and `multiedit` PostToolUse coverage alongside `apply_patch`.
- Forward Codex `transcript_path` into native comment-checker hook input when available.
- Add package smoke coverage for portable hook entrypoints.

### Changed

- Treat the native checker binary as an optional dependency for unsupported platforms.
- Cap child process stdout/stderr captured from the native checker.
- Run CI on Windows in addition to Ubuntu and macOS.

## [0.1.1] - 2026-05-15

### Changed
Expand Down
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

[![ci](https://github.com/code-yeongyu/codex-comment-checker/actions/workflows/ci.yml/badge.svg)](https://github.com/code-yeongyu/codex-comment-checker/actions/workflows/ci.yml) [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Codex plugin that runs [`@code-yeongyu/comment-checker`](https://github.com/code-yeongyu/go-claude-code-comment-checker) after successful `apply_patch` tool calls.
Codex plugin that runs [`@code-yeongyu/comment-checker`](https://github.com/code-yeongyu/go-claude-code-comment-checker) after successful edit-like `PostToolUse` hook calls.

## Behavior

| Case | Result |
|------|--------|
| `apply_patch` succeeds | parses `tool_input.command` and checks added/updated files |
| non-`apply_patch` edit tool succeeds | ignored |
| `write`, `edit`, `multi_edit`, or `multiedit` succeeds | maps the Codex payload to the native checker hook input |
| non-edit tool succeeds | ignored |
| checker exits `2` | returns Codex `PostToolUse` blocking feedback so the model fixes or explains the warning |
| checker binary missing | emits no hook output |
| checker binary missing or unavailable on the current platform | emits no hook output |
| checker exits unexpectedly | leaves hook output unchanged |

Deletes are ignored because they cannot introduce new comments.
Expand All @@ -27,7 +28,7 @@ The plugin ships:
The hook command is:

```bash
node "$PLUGIN_ROOT/dist/cli.js" hook post-tool-use
node "${PLUGIN_ROOT}/dist/cli.js" hook post-tool-use
```

No MCP server or `comment_check` tool is exposed.
Expand Down Expand Up @@ -57,23 +58,27 @@ codex plugin marketplace add /path/to/codex-plugins
node /path/to/codex-plugins/scripts/install-local.mjs /path/to/codex-plugins
```

If your local Codex build exposes plugin install commands, you can install from the UI or CLI instead. For older local builds, the marketplace installer builds and copies the plugin into `~/.codex/plugins/cache/<marketplace>/codex-comment-checker/0.1.0`, installs runtime dependencies there, and enables:
If your local Codex build exposes plugin install commands, you can install from the UI or CLI instead. For older local builds, the marketplace installer builds and copies the plugin into `~/.codex/plugins/cache/<marketplace>/codex-comment-checker/0.1.1`, installs runtime dependencies there, and enables:

```toml
[features]
plugins = true
plugin_hooks = true

[plugins."codex-comment-checker@code-yeongyu-codex-plugins"]
enabled = true
```

## Branch Rules and Releases

- `main` is protected by `.github/branch-ruleset.json`.
- CI runs Node 20 and 22 on Ubuntu and macOS.
- CI runs Node 20 and 22 on Ubuntu, macOS, and Windows.
- Releases are GitHub Releases tagged as `v<semver>`.
- Publishing runs from the `publish` workflow after a GitHub Release is published.

## Privacy

This plugin runs locally. It sends hook input to the local `comment-checker` binary and does not call a network service by itself.
This plugin runs locally. It sends hook input to the optional local `comment-checker` binary when available and does not call a network service by itself.

## License

Expand Down
25 changes: 22 additions & 3 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
"rules": {
"recommended": true,
"style": {
"noNonNullAssertion": "off",
"noDefaultExport": "error",
"noEnum": "error",
"noNonNullAssertion": "error",
"useImportType": "error",
"useConst": "error",
"useNodejsImportProtocol": "off"
},
"complexity": {
"useLiteralKeys": "off"
},
"suspicious": {
"noExplicitAny": "error",
"noTsIgnore": "error",
"noControlCharactersInRegex": "off",
"noEmptyInterface": "off"
}
Expand All @@ -24,6 +31,18 @@
"lineWidth": 120
},
"files": {
"includes": ["src/**/*.ts", "test/**/*.ts", "!**/node_modules/**/*", "!**/dist/**/*"]
}
"includes": ["src/**/*.ts", "test/**/*.ts", "vitest.config.ts", "!**/node_modules/**/*", "!**/dist/**/*"]
},
"overrides": [
{
"includes": ["vitest.config.ts"],
"linter": {
"rules": {
"style": {
"noDefaultExport": "off"
}
}
}
}
]
}
1 change: 0 additions & 1 deletion dist/cli.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
#!/usr/bin/env node
export {};
//# sourceMappingURL=cli.d.ts.map
1 change: 0 additions & 1 deletion dist/cli.d.ts.map

This file was deleted.

1 change: 0 additions & 1 deletion dist/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ else {
process.stderr.write("Usage: codex-comment-checker hook post-tool-use\n");
process.exitCode = 2;
}
//# sourceMappingURL=cli.js.map
1 change: 0 additions & 1 deletion dist/cli.js.map

This file was deleted.

2 changes: 1 addition & 1 deletion dist/codex-hook.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ export type CodexHookOptions = {
export declare function extractCodexCommentCheckRequests(input: CodexPostToolUseInput): CommentCheckRequest[];
export declare function runCommentCheckerPostToolUse(input: CodexPostToolUseInput, options?: CodexHookOptions): Promise<string>;
export declare function runCodexHookCli(): Promise<void>;
//# sourceMappingURL=codex-hook.d.ts.map
export declare function parseCodexPostToolUseInput(input: string): CodexPostToolUseInput | undefined;
1 change: 0 additions & 1 deletion dist/codex-hook.d.ts.map

This file was deleted.

53 changes: 34 additions & 19 deletions dist/codex-hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ export async function runCommentCheckerPostToolUse(input, options = {}) {
const runner = options.run ?? runCommentChecker;
const warnings = [];
for (const request of requests) {
const result = await runner(toHookInput(request, { sessionId: input.session_id, cwd: input.cwd }));
const context = {
sessionId: input.session_id,
cwd: input.cwd,
...(input.transcript_path === null ? {} : { transcriptPath: input.transcript_path }),
};
const result = await runner(toHookInput(request, context));
if (result.status === "missing" || result.status === "pass")
continue;
if (result.status === "error")
Expand All @@ -32,15 +37,25 @@ export async function runCodexHookCli() {
const input = await readStdin();
if (input.trim().length === 0)
return;
const parsed = JSON.parse(input);
if (!isCodexPostToolUseInput(parsed))
const parsed = parseCodexPostToolUseInput(input);
if (!parsed)
return;
const output = await runCommentCheckerPostToolUse(parsed);
if (output.length > 0) {
processStdout.write(output);
processStdout.write("\n");
}
}
export function parseCodexPostToolUseInput(input) {
let parsed;
try {
parsed = JSON.parse(input);
}
catch {
return undefined;
}
return isCodexPostToolUseInput(parsed) ? parsed : undefined;
}
function toToolResultLike(input) {
return {
toolName: input.tool_name,
Expand All @@ -51,11 +66,11 @@ function toToolResultLike(input) {
};
}
function normalizeToolInput(toolName, toolInput) {
if (toolName === "apply_patch" && typeof toolInput.command === "string") {
if (toolName === "apply_patch" && typeof toolInput["command"] === "string") {
return {
...toolInput,
input: toolInput.command,
patch: toolInput.command,
input: toolInput["command"],
patch: toolInput["command"],
};
}
return toolInput;
Expand All @@ -64,13 +79,13 @@ function normalizeToolResponse(toolResponse) {
if (typeof toolResponse === "string") {
return [{ type: "text", text: toolResponse }];
}
if (isRecord(toolResponse) && typeof toolResponse.text === "string") {
return [{ type: "text", text: toolResponse.text }];
if (isRecord(toolResponse) && typeof toolResponse["text"] === "string") {
return [{ type: "text", text: toolResponse["text"] }];
}
return [];
}
function isErrorResponse(toolResponse) {
return isRecord(toolResponse) && toolResponse.is_error === true;
return isRecord(toolResponse) && toolResponse["is_error"] === true;
}
function formatWarnings(warnings) {
return warnings
Expand All @@ -79,15 +94,16 @@ function formatWarnings(warnings) {
}
function isCodexPostToolUseInput(value) {
return (isRecord(value) &&
value.hook_event_name === "PostToolUse" &&
typeof value.session_id === "string" &&
typeof value.turn_id === "string" &&
typeof value.cwd === "string" &&
typeof value.model === "string" &&
typeof value.permission_mode === "string" &&
typeof value.tool_name === "string" &&
isRecord(value.tool_input) &&
typeof value.tool_use_id === "string");
value["hook_event_name"] === "PostToolUse" &&
typeof value["session_id"] === "string" &&
typeof value["turn_id"] === "string" &&
(typeof value["transcript_path"] === "string" || value["transcript_path"] === null) &&
typeof value["cwd"] === "string" &&
typeof value["model"] === "string" &&
typeof value["permission_mode"] === "string" &&
typeof value["tool_name"] === "string" &&
isRecord(value["tool_input"]) &&
typeof value["tool_use_id"] === "string");
}
function readStdin() {
return new Promise((resolve, reject) => {
Expand All @@ -102,4 +118,3 @@ function readStdin() {
});
});
}
//# sourceMappingURL=codex-hook.js.map
1 change: 0 additions & 1 deletion dist/codex-hook.js.map

This file was deleted.

9 changes: 7 additions & 2 deletions dist/core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ export type ImageContent = {
data: string;
mimeType: string;
};
export type CheckerToolName = "Write" | "Edit";
export type CheckerToolName = "Write" | "Edit" | "MultiEdit";
export type CheckerEdit = {
old_string: string;
new_string: string;
};
export type CheckerToolInput = {
file_path: string;
content?: string;
old_string?: string;
new_string?: string;
edits?: CheckerEdit[];
};
export type CommentCheckRequest = {
sourceToolName: string;
Expand Down Expand Up @@ -40,8 +45,8 @@ export declare function extractCommentCheckRequests(event: ToolResultLike): Comm
export declare function toHookInput(request: CommentCheckRequest, context: {
sessionId: string;
cwd: string;
transcriptPath?: string;
}): CommentCheckerHookInput;
export declare function isToolFailureOutput(text: string): boolean;
export declare function parseApplyPatchRequests(patch: string, sourceToolName?: string): CommentCheckRequest[];
export declare function isRecord(value: unknown): value is Record<string, unknown>;
//# sourceMappingURL=core.d.ts.map
1 change: 0 additions & 1 deletion dist/core.d.ts.map

This file was deleted.

Loading
Loading