refactor(packages): extract comment-checker-core package
This commit is contained in:
@@ -34,3 +34,22 @@
|
|||||||
- Core should own `buildSgArgs()` + `runSg()` orchestration and error mapping.
|
- Core should own `buildSgArgs()` + `runSg()` orchestration and error mapping.
|
||||||
- OMO-specific binary resolution stays adapter-side (`getAstGrepPath` in `cli-binary-path-resolution.ts`).
|
- OMO-specific binary resolution stays adapter-side (`getAstGrepPath` in `cli-binary-path-resolution.ts`).
|
||||||
- OMO-specific process spawn stays adapter-side (`bun-spawn-shim.ts`), injected via core deps (`spawnProcess`).
|
- OMO-specific process spawn stays adapter-side (`bun-spawn-shim.ts`), injected via core deps (`spawnProcess`).
|
||||||
|
|
||||||
|
## [2026-05-21T00:00:00Z] Task 8 (worktree)
|
||||||
|
- Created `packages/comment-checker-core/` with `package.json`, `tsconfig.json`, `index.d.ts`, and `src/` barrel.
|
||||||
|
- Moved pure apply-patch parser + metadata parsing into `packages/comment-checker-core/src/apply-patch-edits.ts`.
|
||||||
|
- Moved shared comment-checker types into `packages/comment-checker-core/src/types.ts`.
|
||||||
|
- Added injectable pure runner in `packages/comment-checker-core/src/runner.ts`:
|
||||||
|
- `resolveCommentCheckerBinary()`
|
||||||
|
- `runCommentChecker()` with injected `spawn`, `existsSync`, and timer functions.
|
||||||
|
- Kept OMO-specific adapter pieces in place (`hook.ts`, `pending-calls.ts`, `initialization-gate.ts`, `downloader.ts`).
|
||||||
|
- Added per-file shims at original locations:
|
||||||
|
- `src/hooks/comment-checker/apply-patch-edits.ts`
|
||||||
|
- `src/hooks/comment-checker/types.ts`
|
||||||
|
- Updated `src/hooks/comment-checker/cli.ts` to keep Bun spawn glue locally while delegating pure runner + resolver to core package.
|
||||||
|
- Updated `src/hooks/comment-checker/hook.ts` to import `extractApplyPatchEdits` from `@oh-my-opencode/comment-checker-core`.
|
||||||
|
- Updated root workspace wiring (`package.json` workspaces, devDependency, typecheck:packages) and ran `bun install`.
|
||||||
|
- Verification:
|
||||||
|
- `bun run typecheck` exit 0
|
||||||
|
- `bun test` 7312/1/2/7315 (baseline-matching drift)
|
||||||
|
- `bun run build` exit 0
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@oh-my-opencode/ast-grep-core": "workspace:*",
|
"@oh-my-opencode/ast-grep-core": "workspace:*",
|
||||||
"@oh-my-opencode/ast-grep-mcp": "workspace:*",
|
"@oh-my-opencode/ast-grep-mcp": "workspace:*",
|
||||||
|
"@oh-my-opencode/comment-checker-core": "workspace:*",
|
||||||
"@oh-my-opencode/rules-core": "workspace:*",
|
"@oh-my-opencode/rules-core": "workspace:*",
|
||||||
"@oh-my-opencode/utils": "workspace:*",
|
"@oh-my-opencode/utils": "workspace:*",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
@@ -70,6 +71,10 @@
|
|||||||
"bun-types": "1.3.12",
|
"bun-types": "1.3.12",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"packages/comment-checker-core": {
|
||||||
|
"name": "@oh-my-opencode/comment-checker-core",
|
||||||
|
"version": "0.1.0",
|
||||||
|
},
|
||||||
"packages/rules-core": {
|
"packages/rules-core": {
|
||||||
"name": "@oh-my-opencode/rules-core",
|
"name": "@oh-my-opencode/rules-core",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
@@ -161,6 +166,8 @@
|
|||||||
|
|
||||||
"@oh-my-opencode/ast-grep-mcp": ["@oh-my-opencode/ast-grep-mcp@workspace:packages/ast-grep-mcp"],
|
"@oh-my-opencode/ast-grep-mcp": ["@oh-my-opencode/ast-grep-mcp@workspace:packages/ast-grep-mcp"],
|
||||||
|
|
||||||
|
"@oh-my-opencode/comment-checker-core": ["@oh-my-opencode/comment-checker-core@workspace:packages/comment-checker-core"],
|
||||||
|
|
||||||
"@oh-my-opencode/rules-core": ["@oh-my-opencode/rules-core@workspace:packages/rules-core"],
|
"@oh-my-opencode/rules-core": ["@oh-my-opencode/rules-core@workspace:packages/rules-core"],
|
||||||
|
|
||||||
"@oh-my-opencode/utils": ["@oh-my-opencode/utils@workspace:packages/utils"],
|
"@oh-my-opencode/utils": ["@oh-my-opencode/utils@workspace:packages/utils"],
|
||||||
|
|||||||
+4
-2
@@ -9,7 +9,8 @@
|
|||||||
"packages/rules-core",
|
"packages/rules-core",
|
||||||
"packages/ast-grep-core",
|
"packages/ast-grep-core",
|
||||||
"packages/ast-grep-mcp",
|
"packages/ast-grep-mcp",
|
||||||
"packages/utils"
|
"packages/utils",
|
||||||
|
"packages/comment-checker-core"
|
||||||
],
|
],
|
||||||
"bin": {
|
"bin": {
|
||||||
"oh-my-opencode": "bin/oh-my-opencode.js",
|
"oh-my-opencode": "bin/oh-my-opencode.js",
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
"prepublishOnly": "bun run clean && bun run build:lsp-tools-mcp && bun run build",
|
"prepublishOnly": "bun run clean && bun run build:lsp-tools-mcp && bun run build",
|
||||||
"test:model-capabilities": "bun test src/shared/model-capability-aliases.test.ts src/shared/model-capability-guardrails.test.ts src/shared/model-capabilities.test.ts src/cli/doctor/checks/model-resolution.test.ts --bail",
|
"test:model-capabilities": "bun test src/shared/model-capability-aliases.test.ts src/shared/model-capability-guardrails.test.ts src/shared/model-capabilities.test.ts src/cli/doctor/checks/model-resolution.test.ts --bail",
|
||||||
"typecheck": "tsgo --noEmit && bun run typecheck:packages",
|
"typecheck": "tsgo --noEmit && bun run typecheck:packages",
|
||||||
"typecheck:packages": "tsgo --noEmit -p packages/rules-core/tsconfig.json && tsgo --noEmit -p packages/ast-grep-core/tsconfig.json && tsgo --noEmit -p packages/ast-grep-mcp/tsconfig.json && tsgo --noEmit -p packages/utils/tsconfig.json",
|
"typecheck:packages": "tsgo --noEmit -p packages/rules-core/tsconfig.json && tsgo --noEmit -p packages/ast-grep-core/tsconfig.json && tsgo --noEmit -p packages/ast-grep-mcp/tsconfig.json && tsgo --noEmit -p packages/utils/tsconfig.json && tsgo --noEmit -p packages/comment-checker-core/tsconfig.json",
|
||||||
"typecheck:script": "tsgo --noEmit -p script/tsconfig.json",
|
"typecheck:script": "tsgo --noEmit -p script/tsconfig.json",
|
||||||
"test": "bun test",
|
"test": "bun test",
|
||||||
"build:ast-grep-mcp": "bun run --cwd packages/ast-grep-mcp build"
|
"build:ast-grep-mcp": "bun run --cwd packages/ast-grep-mcp build"
|
||||||
@@ -90,6 +91,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@oh-my-opencode/ast-grep-core": "workspace:*",
|
"@oh-my-opencode/ast-grep-core": "workspace:*",
|
||||||
"@oh-my-opencode/ast-grep-mcp": "workspace:*",
|
"@oh-my-opencode/ast-grep-mcp": "workspace:*",
|
||||||
|
"@oh-my-opencode/comment-checker-core": "workspace:*",
|
||||||
"@oh-my-opencode/rules-core": "workspace:*",
|
"@oh-my-opencode/rules-core": "workspace:*",
|
||||||
"@oh-my-opencode/utils": "workspace:*",
|
"@oh-my-opencode/utils": "workspace:*",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20260518.1",
|
"@typescript/native-preview": "7.0.0-dev.20260518.1",
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./src/index"
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@oh-my-opencode/comment-checker-core",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"description": "Pure TypeScript comment-checker parsing and runner core for oh-my-opencode.",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"import": "./src/index.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsgo --noEmit -p tsconfig.json",
|
||||||
|
"test": "bun test src/*.test.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import type { ApplyPatchAccumulator, ApplyPatchFileMetadata, CheckerEdit } from "./types"
|
||||||
|
|
||||||
|
export function extractApplyPatchEdits(
|
||||||
|
details: unknown,
|
||||||
|
args?: Record<string, unknown>,
|
||||||
|
): CheckerEdit[] {
|
||||||
|
const metadataEdits = getApplyPatchMetadataFiles(details)
|
||||||
|
.filter((file) => file.type?.toLowerCase() !== "delete")
|
||||||
|
.map((file) => ({
|
||||||
|
filePath: file.movePath ?? file.filePath,
|
||||||
|
before: file.before,
|
||||||
|
after: file.after,
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (metadataEdits.length > 0) return metadataEdits
|
||||||
|
|
||||||
|
const patch = args === undefined ? undefined : getString(args, ["patchText", "input", "patch", "command"])
|
||||||
|
if (patch === undefined) return []
|
||||||
|
|
||||||
|
return parseApplyPatchRequests(patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getApplyPatchMetadataFiles(details: unknown): ApplyPatchFileMetadata[] {
|
||||||
|
if (!isRecord(details)) return []
|
||||||
|
|
||||||
|
const direct = readApplyPatchMetadataFiles(details["files"])
|
||||||
|
if (direct.length > 0) return direct
|
||||||
|
|
||||||
|
const resultDetails = details["result"]
|
||||||
|
const result = isRecord(resultDetails) ? readApplyPatchMetadataFiles(resultDetails["files"]) : []
|
||||||
|
if (result.length > 0) return result
|
||||||
|
|
||||||
|
const metadataDetails = details["metadata"]
|
||||||
|
return isRecord(metadataDetails) ? readApplyPatchMetadataFiles(metadataDetails["files"]) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readApplyPatchMetadataFiles(value: unknown): ApplyPatchFileMetadata[] {
|
||||||
|
if (!Array.isArray(value)) return []
|
||||||
|
|
||||||
|
const files: ApplyPatchFileMetadata[] = []
|
||||||
|
for (const item of value) {
|
||||||
|
if (!isRecord(item)) continue
|
||||||
|
|
||||||
|
const filePath = getString(item, ["filePath", "file_path", "path"])
|
||||||
|
const movePath = getString(item, ["movePath", "move_path"])
|
||||||
|
const before = getString(item, ["before", "old", "oldString", "old_string"])
|
||||||
|
const after = getString(item, ["after", "new", "newString", "new_string"])
|
||||||
|
const type = getString(item, ["type", "operation"])
|
||||||
|
|
||||||
|
if (filePath === undefined || before === undefined || after === undefined) continue
|
||||||
|
|
||||||
|
files.push({
|
||||||
|
filePath,
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
...(movePath === undefined ? {} : { movePath }),
|
||||||
|
...(type === undefined ? {} : { type }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseApplyPatchRequests(patch: string): CheckerEdit[] {
|
||||||
|
const edits: CheckerEdit[] = []
|
||||||
|
let current: ApplyPatchAccumulator | undefined
|
||||||
|
|
||||||
|
const flush = (): void => {
|
||||||
|
if (current === undefined) return
|
||||||
|
|
||||||
|
if (current.operation === "add") {
|
||||||
|
const after = joinPatchLines(current.newLines)
|
||||||
|
if (after.length > 0) {
|
||||||
|
edits.push({ filePath: current.filePath, before: "", after })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.operation === "update") {
|
||||||
|
const after = joinPatchLines(current.newLines)
|
||||||
|
if (after.length > 0) {
|
||||||
|
edits.push({
|
||||||
|
filePath: current.movePath ?? current.filePath,
|
||||||
|
before: joinPatchLines(current.oldLines),
|
||||||
|
after,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const line of patch.split(/\r?\n/)) {
|
||||||
|
if (line === "*** Begin Patch" || line === "*** End Patch") continue
|
||||||
|
|
||||||
|
if (line.startsWith("*** Add File: ")) {
|
||||||
|
flush()
|
||||||
|
current = makeAccumulator("add", line.slice("*** Add File: ".length).trim())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("*** Update File: ")) {
|
||||||
|
flush()
|
||||||
|
current = makeAccumulator("update", line.slice("*** Update File: ".length).trim())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("*** Delete File: ")) {
|
||||||
|
flush()
|
||||||
|
current = makeAccumulator("delete", line.slice("*** Delete File: ".length).trim())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("*** Move to: ")) {
|
||||||
|
if (current?.operation === "update") {
|
||||||
|
current.movePath = line.slice("*** Move to: ".length).trim()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current === undefined || line.startsWith("@@")) continue
|
||||||
|
|
||||||
|
if (current.operation === "add") {
|
||||||
|
if (line.startsWith("+")) current.newLines.push(line.slice(1))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.operation === "update") {
|
||||||
|
if (line.startsWith("-")) current.oldLines.push(line.slice(1))
|
||||||
|
if (line.startsWith("+")) current.newLines.push(line.slice(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flush()
|
||||||
|
return edits
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeAccumulator(
|
||||||
|
operation: ApplyPatchAccumulator["operation"],
|
||||||
|
filePath: string,
|
||||||
|
): ApplyPatchAccumulator {
|
||||||
|
return {
|
||||||
|
operation,
|
||||||
|
filePath,
|
||||||
|
oldLines: [],
|
||||||
|
newLines: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getString(input: Record<string, unknown>, keys: readonly string[]): string | undefined {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = input[key]
|
||||||
|
if (typeof value === "string") return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function joinPatchLines(lines: readonly string[]): string {
|
||||||
|
return lines.length === 0 ? "" : `${lines.join("\n")}\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
export {
|
||||||
|
extractApplyPatchEdits,
|
||||||
|
getApplyPatchMetadataFiles,
|
||||||
|
getString,
|
||||||
|
isRecord,
|
||||||
|
joinPatchLines,
|
||||||
|
makeAccumulator,
|
||||||
|
parseApplyPatchRequests,
|
||||||
|
readApplyPatchMetadataFiles,
|
||||||
|
} from "./apply-patch-edits"
|
||||||
|
export { resolveCommentCheckerBinary, runCommentChecker } from "./runner"
|
||||||
|
export type {
|
||||||
|
ApplyPatchAccumulator,
|
||||||
|
ApplyPatchFileMetadata,
|
||||||
|
CheckResult,
|
||||||
|
CheckerEdit,
|
||||||
|
CommentFilter,
|
||||||
|
CommentInfo,
|
||||||
|
CommentType,
|
||||||
|
FileComments,
|
||||||
|
FilterResult,
|
||||||
|
HookInput,
|
||||||
|
PendingCall,
|
||||||
|
ResolveCommentCheckerBinaryInput,
|
||||||
|
RunCommentCheckerInput,
|
||||||
|
RunCommentCheckerOptions,
|
||||||
|
SpawnFn,
|
||||||
|
SpawnProcess,
|
||||||
|
SpawnSignal,
|
||||||
|
} from "./types"
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { createRequire } from "node:module"
|
||||||
|
import { dirname, join } from "node:path"
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CheckResult,
|
||||||
|
ResolveCommentCheckerBinaryInput,
|
||||||
|
RunCommentCheckerInput,
|
||||||
|
RunCommentCheckerOptions,
|
||||||
|
SpawnProcess,
|
||||||
|
SpawnSignal,
|
||||||
|
} from "./types"
|
||||||
|
|
||||||
|
const EMPTY_RESULT: CheckResult = { hasComments: false, message: "" }
|
||||||
|
|
||||||
|
function killProcessSafely(process: SpawnProcess, signal: SpawnSignal): void {
|
||||||
|
try {
|
||||||
|
process.kill(signal)
|
||||||
|
} catch (error) {
|
||||||
|
if (!(error instanceof Error)) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveCommentCheckerBinary(input: ResolveCommentCheckerBinaryInput): string | null {
|
||||||
|
const packageName = input.packageName ?? "@code-yeongyu/comment-checker"
|
||||||
|
|
||||||
|
if (input.cachedBinaryPath !== null && input.existsSync(input.cachedBinaryPath)) {
|
||||||
|
return input.cachedBinaryPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.importMetaUrl === undefined) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const require = createRequire(input.importMetaUrl)
|
||||||
|
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
||||||
|
const binaryPath = join(dirname(packageJsonPath), "bin", input.binaryName)
|
||||||
|
return input.existsSync(binaryPath) ? binaryPath : null
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runCommentChecker(
|
||||||
|
input: RunCommentCheckerInput,
|
||||||
|
options: RunCommentCheckerOptions,
|
||||||
|
): Promise<CheckResult> {
|
||||||
|
if (input.binaryPath === null || !options.existsSync(input.binaryPath)) {
|
||||||
|
return EMPTY_RESULT
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = [input.binaryPath, "check"]
|
||||||
|
if (input.customPrompt !== undefined) {
|
||||||
|
args.push("--prompt", input.customPrompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutMs = options.timeoutMs ?? 30_000
|
||||||
|
const killGraceMs = options.killGraceMs ?? 1_000
|
||||||
|
const setTimer = options.setTimeoutFn ?? setTimeout
|
||||||
|
const clearTimer = options.clearTimeoutFn ?? clearTimeout
|
||||||
|
|
||||||
|
const process = options.spawn(args)
|
||||||
|
process.stdin.write(JSON.stringify(input.hookInput))
|
||||||
|
process.stdin.end()
|
||||||
|
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let graceId: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
const timeoutPromise = new Promise<"timeout">((resolve) => {
|
||||||
|
timeoutId = setTimer(() => {
|
||||||
|
killProcessSafely(process, "SIGTERM")
|
||||||
|
|
||||||
|
graceId = setTimer(() => {
|
||||||
|
killProcessSafely(process, "SIGKILL")
|
||||||
|
}, killGraceMs)
|
||||||
|
|
||||||
|
resolve("timeout")
|
||||||
|
}, timeoutMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stdoutPromise = new Response(process.stdout).text()
|
||||||
|
const stderrPromise = new Response(process.stderr).text()
|
||||||
|
const exitCodePromise = process.exited
|
||||||
|
const completed = Promise.all([stdoutPromise, stderrPromise, exitCodePromise] as const)
|
||||||
|
const race = await Promise.race([completed, timeoutPromise] as const)
|
||||||
|
|
||||||
|
if (race === "timeout") {
|
||||||
|
return EMPTY_RESULT
|
||||||
|
}
|
||||||
|
|
||||||
|
const [_stdout, stderr, exitCode] = race
|
||||||
|
if (exitCode === 0) {
|
||||||
|
return EMPTY_RESULT
|
||||||
|
}
|
||||||
|
if (exitCode === 2) {
|
||||||
|
return { hasComments: true, message: stderr }
|
||||||
|
}
|
||||||
|
|
||||||
|
return EMPTY_RESULT
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return EMPTY_RESULT
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
if (timeoutId !== null) {
|
||||||
|
clearTimer(timeoutId)
|
||||||
|
}
|
||||||
|
if (graceId !== null) {
|
||||||
|
clearTimer(graceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
export type CheckerEdit = {
|
||||||
|
readonly filePath: string
|
||||||
|
readonly before: string
|
||||||
|
readonly after: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApplyPatchFileMetadata = {
|
||||||
|
readonly filePath: string
|
||||||
|
readonly movePath?: string
|
||||||
|
readonly before: string
|
||||||
|
readonly after: string
|
||||||
|
readonly type?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApplyPatchAccumulator = {
|
||||||
|
operation: "add" | "update" | "delete"
|
||||||
|
filePath: string
|
||||||
|
movePath?: string
|
||||||
|
oldLines: string[]
|
||||||
|
newLines: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommentType = "line" | "block" | "docstring"
|
||||||
|
|
||||||
|
export interface CommentInfo {
|
||||||
|
readonly text: string
|
||||||
|
readonly lineNumber: number
|
||||||
|
readonly filePath: string
|
||||||
|
readonly commentType: CommentType
|
||||||
|
readonly isDocstring: boolean
|
||||||
|
readonly metadata?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PendingCall {
|
||||||
|
readonly filePath: string
|
||||||
|
readonly content?: string
|
||||||
|
readonly oldString?: string
|
||||||
|
readonly newString?: string
|
||||||
|
readonly edits?: readonly { old_string: string; new_string: string }[]
|
||||||
|
readonly tool: "write" | "edit" | "multiedit"
|
||||||
|
readonly sessionID: string
|
||||||
|
readonly timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileComments {
|
||||||
|
readonly filePath: string
|
||||||
|
readonly comments: readonly CommentInfo[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterResult {
|
||||||
|
readonly shouldSkip: boolean
|
||||||
|
readonly reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommentFilter = (comment: CommentInfo) => FilterResult
|
||||||
|
|
||||||
|
export interface HookInput {
|
||||||
|
readonly session_id: string
|
||||||
|
readonly tool_name: string
|
||||||
|
readonly transcript_path: string
|
||||||
|
readonly cwd: string
|
||||||
|
readonly hook_event_name: string
|
||||||
|
readonly tool_input: {
|
||||||
|
readonly file_path?: string
|
||||||
|
readonly content?: string
|
||||||
|
readonly old_string?: string
|
||||||
|
readonly new_string?: string
|
||||||
|
readonly edits?: readonly { old_string: string; new_string: string }[]
|
||||||
|
}
|
||||||
|
readonly tool_response?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CheckResult {
|
||||||
|
readonly hasComments: boolean
|
||||||
|
readonly message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SpawnSignal = "SIGTERM" | "SIGKILL"
|
||||||
|
|
||||||
|
export type SpawnProcess = {
|
||||||
|
readonly stdin: {
|
||||||
|
write(input: string): void
|
||||||
|
end(): void
|
||||||
|
}
|
||||||
|
readonly stdout: ReadableStream<Uint8Array>
|
||||||
|
readonly stderr: ReadableStream<Uint8Array>
|
||||||
|
readonly exited: Promise<number>
|
||||||
|
kill(signal: SpawnSignal): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SpawnFn = (args: readonly string[]) => SpawnProcess
|
||||||
|
|
||||||
|
export interface ResolveCommentCheckerBinaryInput {
|
||||||
|
readonly binaryName: string
|
||||||
|
readonly cachedBinaryPath: string | null
|
||||||
|
readonly existsSync: (path: string) => boolean
|
||||||
|
readonly importMetaUrl?: string
|
||||||
|
readonly packageName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RunCommentCheckerInput {
|
||||||
|
readonly hookInput: HookInput
|
||||||
|
readonly binaryPath: string | null
|
||||||
|
readonly customPrompt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RunCommentCheckerOptions {
|
||||||
|
readonly spawn: SpawnFn
|
||||||
|
readonly existsSync: (path: string) => boolean
|
||||||
|
readonly timeoutMs?: number
|
||||||
|
readonly killGraceMs?: number
|
||||||
|
readonly setTimeoutFn?: typeof setTimeout
|
||||||
|
readonly clearTimeoutFn?: typeof clearTimeout
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"types": ["bun-types"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
@@ -1,180 +1,21 @@
|
|||||||
import type { ApplyPatchEdit } from "./cli-runner"
|
import {
|
||||||
|
extractApplyPatchEdits,
|
||||||
|
getApplyPatchMetadataFiles,
|
||||||
|
getString,
|
||||||
|
isRecord,
|
||||||
|
joinPatchLines,
|
||||||
|
makeAccumulator,
|
||||||
|
parseApplyPatchRequests,
|
||||||
|
readApplyPatchMetadataFiles,
|
||||||
|
} from "@oh-my-opencode/comment-checker-core"
|
||||||
|
|
||||||
type ApplyPatchFileMetadata = {
|
export {
|
||||||
readonly filePath: string
|
extractApplyPatchEdits,
|
||||||
readonly movePath?: string
|
getApplyPatchMetadataFiles,
|
||||||
readonly before: string
|
getString,
|
||||||
readonly after: string
|
isRecord,
|
||||||
readonly type?: string
|
joinPatchLines,
|
||||||
}
|
makeAccumulator,
|
||||||
|
parseApplyPatchRequests,
|
||||||
type ApplyPatchAccumulator = {
|
readApplyPatchMetadataFiles,
|
||||||
operation: "add" | "update" | "delete"
|
|
||||||
filePath: string
|
|
||||||
movePath?: string
|
|
||||||
oldLines: string[]
|
|
||||||
newLines: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extractApplyPatchEdits(
|
|
||||||
details: unknown,
|
|
||||||
args?: Record<string, unknown>,
|
|
||||||
): ApplyPatchEdit[] {
|
|
||||||
const metadataEdits = getApplyPatchMetadataFiles(details)
|
|
||||||
.filter((file) => file.type?.toLowerCase() !== "delete")
|
|
||||||
.map((file) => ({
|
|
||||||
filePath: file.movePath ?? file.filePath,
|
|
||||||
before: file.before,
|
|
||||||
after: file.after,
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (metadataEdits.length > 0) return metadataEdits
|
|
||||||
|
|
||||||
const patch = args === undefined ? undefined : getString(args, ["patchText", "input", "patch", "command"])
|
|
||||||
if (patch === undefined) return []
|
|
||||||
|
|
||||||
return parseApplyPatchEdits(patch)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getApplyPatchMetadataFiles(details: unknown): ApplyPatchFileMetadata[] {
|
|
||||||
if (!isRecord(details)) return []
|
|
||||||
|
|
||||||
const direct = readApplyPatchMetadataFiles(details["files"])
|
|
||||||
if (direct.length > 0) return direct
|
|
||||||
|
|
||||||
const resultDetails = details["result"]
|
|
||||||
const result = isRecord(resultDetails) ? readApplyPatchMetadataFiles(resultDetails["files"]) : []
|
|
||||||
if (result.length > 0) return result
|
|
||||||
|
|
||||||
const metadataDetails = details["metadata"]
|
|
||||||
return isRecord(metadataDetails) ? readApplyPatchMetadataFiles(metadataDetails["files"]) : []
|
|
||||||
}
|
|
||||||
|
|
||||||
function readApplyPatchMetadataFiles(value: unknown): ApplyPatchFileMetadata[] {
|
|
||||||
if (!Array.isArray(value)) return []
|
|
||||||
|
|
||||||
const files: ApplyPatchFileMetadata[] = []
|
|
||||||
for (const item of value) {
|
|
||||||
if (!isRecord(item)) continue
|
|
||||||
|
|
||||||
const filePath = getString(item, ["filePath", "file_path", "path"])
|
|
||||||
const movePath = getString(item, ["movePath", "move_path"])
|
|
||||||
const before = getString(item, ["before", "old", "oldString", "old_string"])
|
|
||||||
const after = getString(item, ["after", "new", "newString", "new_string"])
|
|
||||||
const type = getString(item, ["type", "operation"])
|
|
||||||
|
|
||||||
if (filePath === undefined || before === undefined || after === undefined) continue
|
|
||||||
|
|
||||||
files.push({
|
|
||||||
filePath,
|
|
||||||
before,
|
|
||||||
after,
|
|
||||||
...(movePath === undefined ? {} : { movePath }),
|
|
||||||
...(type === undefined ? {} : { type }),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseApplyPatchEdits(patch: string): ApplyPatchEdit[] {
|
|
||||||
const edits: ApplyPatchEdit[] = []
|
|
||||||
let current: ApplyPatchAccumulator | undefined
|
|
||||||
|
|
||||||
const flush = (): void => {
|
|
||||||
if (current === undefined) return
|
|
||||||
|
|
||||||
if (current.operation === "add") {
|
|
||||||
const after = joinPatchLines(current.newLines)
|
|
||||||
if (after.length > 0) {
|
|
||||||
edits.push({ filePath: current.filePath, before: "", after })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current.operation === "update") {
|
|
||||||
const after = joinPatchLines(current.newLines)
|
|
||||||
if (after.length > 0) {
|
|
||||||
edits.push({
|
|
||||||
filePath: current.movePath ?? current.filePath,
|
|
||||||
before: joinPatchLines(current.oldLines),
|
|
||||||
after,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
current = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const line of patch.split(/\r?\n/)) {
|
|
||||||
if (line === "*** Begin Patch" || line === "*** End Patch") continue
|
|
||||||
|
|
||||||
if (line.startsWith("*** Add File: ")) {
|
|
||||||
flush()
|
|
||||||
current = makeAccumulator("add", line.slice("*** Add File: ".length).trim())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("*** Update File: ")) {
|
|
||||||
flush()
|
|
||||||
current = makeAccumulator("update", line.slice("*** Update File: ".length).trim())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("*** Delete File: ")) {
|
|
||||||
flush()
|
|
||||||
current = makeAccumulator("delete", line.slice("*** Delete File: ".length).trim())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("*** Move to: ")) {
|
|
||||||
if (current?.operation === "update") {
|
|
||||||
current.movePath = line.slice("*** Move to: ".length).trim()
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current === undefined || line.startsWith("@@")) continue
|
|
||||||
|
|
||||||
if (current.operation === "add") {
|
|
||||||
if (line.startsWith("+")) current.newLines.push(line.slice(1))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current.operation === "update") {
|
|
||||||
if (line.startsWith("-")) current.oldLines.push(line.slice(1))
|
|
||||||
if (line.startsWith("+")) current.newLines.push(line.slice(1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flush()
|
|
||||||
return edits
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeAccumulator(
|
|
||||||
operation: ApplyPatchAccumulator["operation"],
|
|
||||||
filePath: string,
|
|
||||||
): ApplyPatchAccumulator {
|
|
||||||
return {
|
|
||||||
operation,
|
|
||||||
filePath,
|
|
||||||
oldLines: [],
|
|
||||||
newLines: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getString(input: Record<string, unknown>, keys: readonly string[]): string | undefined {
|
|
||||||
for (const key of keys) {
|
|
||||||
const value = input[key]
|
|
||||||
if (typeof value === "string") return value
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function joinPatchLines(lines: readonly string[]): string {
|
|
||||||
return lines.length === 0 ? "" : `${lines.join("\n")}\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return typeof value === "object" && value !== null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { spawn } from "../../shared/bun-spawn-shim"
|
import { spawn } from "../../shared/bun-spawn-shim"
|
||||||
import { createRequire } from "module"
|
import { join } from "path"
|
||||||
import { dirname, join } from "path"
|
|
||||||
import { existsSync } from "fs"
|
import { existsSync } from "fs"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
import { tmpdir } from "os"
|
import { tmpdir } from "os"
|
||||||
|
import {
|
||||||
|
resolveCommentCheckerBinary,
|
||||||
|
runCommentChecker as runCommentCheckerCore,
|
||||||
|
type CheckResult,
|
||||||
|
type HookInput,
|
||||||
|
} from "@oh-my-opencode/comment-checker-core"
|
||||||
import { getCachedBinaryPath, ensureCommentCheckerBinary } from "./downloader"
|
import { getCachedBinaryPath, ensureCommentCheckerBinary } from "./downloader"
|
||||||
|
|
||||||
const DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1"
|
const DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1"
|
||||||
@@ -21,33 +26,15 @@ function getBinaryName(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findCommentCheckerPathSync(): string | null {
|
function findCommentCheckerPathSync(): string | null {
|
||||||
const binaryName = getBinaryName()
|
const resolvedPath = resolveCommentCheckerBinary({
|
||||||
|
binaryName: getBinaryName(),
|
||||||
// Check cached binary first (safest path - no module resolution needed)
|
cachedBinaryPath: getCachedBinaryPath(),
|
||||||
const cachedPath = getCachedBinaryPath()
|
existsSync,
|
||||||
if (cachedPath) {
|
importMetaUrl: import.meta.url,
|
||||||
debugLog("found binary in cache:", cachedPath)
|
})
|
||||||
return cachedPath
|
if (resolvedPath !== null) {
|
||||||
}
|
debugLog("resolved binary path:", resolvedPath)
|
||||||
|
return resolvedPath
|
||||||
// Guard against undefined import.meta.url (can happen on Windows during plugin loading)
|
|
||||||
if (!import.meta.url) {
|
|
||||||
debugLog("import.meta.url is undefined, skipping package resolution")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const require = createRequire(import.meta.url)
|
|
||||||
const cliPkgPath = require.resolve("@code-yeongyu/comment-checker/package.json")
|
|
||||||
const cliDir = dirname(cliPkgPath)
|
|
||||||
const binaryPath = join(cliDir, "bin", binaryName)
|
|
||||||
|
|
||||||
if (existsSync(binaryPath)) {
|
|
||||||
debugLog("found binary in main package:", binaryPath)
|
|
||||||
return binaryPath
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
debugLog("main package not installed or resolution failed:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog("no binary found in known locations")
|
debugLog("no binary found in known locations")
|
||||||
@@ -121,26 +108,7 @@ export function startBackgroundInit(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HookInput {
|
export type { HookInput, CheckResult }
|
||||||
session_id: string
|
|
||||||
tool_name: string
|
|
||||||
transcript_path: string
|
|
||||||
cwd: string
|
|
||||||
hook_event_name: string
|
|
||||||
tool_input: {
|
|
||||||
file_path?: string
|
|
||||||
content?: string
|
|
||||||
old_string?: string
|
|
||||||
new_string?: string
|
|
||||||
edits?: Array<{ old_string: string; new_string: string }>
|
|
||||||
}
|
|
||||||
tool_response?: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CheckResult {
|
|
||||||
hasComments: boolean
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run comment-checker CLI with given input.
|
* Run comment-checker CLI with given input.
|
||||||
@@ -156,98 +124,22 @@ export async function runCommentChecker(input: HookInput, cliPath?: string, cust
|
|||||||
return { hasComments: false, message: "" }
|
return { hasComments: false, message: "" }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existsSync(binaryPath)) {
|
|
||||||
debugLog("comment-checker binary does not exist:", binaryPath)
|
|
||||||
return { hasComments: false, message: "" }
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsonInput = JSON.stringify(input)
|
|
||||||
debugLog("running comment-checker with input:", jsonInput.substring(0, 200))
|
|
||||||
|
|
||||||
let didTimeout = false
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const args = [binaryPath, "check"]
|
const result = await runCommentCheckerCore(
|
||||||
if (customPrompt) {
|
{ hookInput: input, binaryPath, customPrompt },
|
||||||
args.push("--prompt", customPrompt)
|
{
|
||||||
}
|
existsSync,
|
||||||
|
spawn: (args: readonly string[]) =>
|
||||||
const proc = spawn(args, {
|
spawn([...args], {
|
||||||
stdin: "pipe",
|
stdin: "pipe",
|
||||||
stdout: "pipe",
|
stdout: "pipe",
|
||||||
stderr: "pipe",
|
stderr: "pipe",
|
||||||
})
|
}),
|
||||||
|
},
|
||||||
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
)
|
||||||
const timeoutPromise = new Promise<"timeout">(resolve => {
|
return result
|
||||||
timeoutId = setTimeout(async () => {
|
} catch (error) {
|
||||||
didTimeout = true
|
debugLog("failed to run comment-checker:", error)
|
||||||
debugLog("comment-checker timed out after 30s; sending SIGTERM")
|
|
||||||
try {
|
|
||||||
proc.kill("SIGTERM")
|
|
||||||
} catch (err) {
|
|
||||||
debugLog("failed to SIGTERM:", err)
|
|
||||||
}
|
|
||||||
const graceTimer = setTimeout(() => {
|
|
||||||
try {
|
|
||||||
proc.kill("SIGKILL")
|
|
||||||
debugLog("sent SIGKILL after grace period")
|
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
try {
|
|
||||||
await proc.exited
|
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
clearTimeout(graceTimer)
|
|
||||||
resolve("timeout")
|
|
||||||
}, 30_000)
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Write JSON to stdin
|
|
||||||
proc.stdin.write(jsonInput)
|
|
||||||
proc.stdin.end()
|
|
||||||
|
|
||||||
const stdoutPromise = new Response(proc.stdout).text()
|
|
||||||
const stderrPromise = new Response(proc.stderr).text()
|
|
||||||
const exitCodePromise = proc.exited
|
|
||||||
|
|
||||||
const raceResult = await Promise.race([
|
|
||||||
Promise.all([stdoutPromise, stderrPromise, exitCodePromise] as const),
|
|
||||||
timeoutPromise,
|
|
||||||
])
|
|
||||||
|
|
||||||
if (raceResult === "timeout") {
|
|
||||||
return { hasComments: false, message: "" }
|
|
||||||
}
|
|
||||||
|
|
||||||
const [stdout, stderr, exitCode] = raceResult
|
|
||||||
|
|
||||||
debugLog("exit code:", exitCode, "stdout length:", stdout.length, "stderr length:", stderr.length)
|
|
||||||
|
|
||||||
if (exitCode === 0) {
|
|
||||||
return { hasComments: false, message: "" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exitCode === 2) {
|
|
||||||
// Comments detected - message is in stderr
|
|
||||||
return { hasComments: true, message: stderr }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error case
|
|
||||||
debugLog("unexpected exit code:", exitCode, "stderr:", stderr)
|
|
||||||
return { hasComments: false, message: "" }
|
|
||||||
} finally {
|
|
||||||
if (timeoutId !== null) {
|
|
||||||
clearTimeout(timeoutId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (didTimeout) {
|
|
||||||
return { hasComments: false, message: "" }
|
|
||||||
}
|
|
||||||
debugLog("failed to run comment-checker:", err)
|
|
||||||
return { hasComments: false, message: "" }
|
return { hasComments: false, message: "" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
processWithCli,
|
processWithCli,
|
||||||
processApplyPatchEditsWithCli,
|
processApplyPatchEditsWithCli,
|
||||||
} from "./cli-runner"
|
} from "./cli-runner"
|
||||||
import { extractApplyPatchEdits } from "./apply-patch-edits"
|
import { extractApplyPatchEdits } from "@oh-my-opencode/comment-checker-core"
|
||||||
import {
|
import {
|
||||||
registerPendingCall,
|
registerPendingCall,
|
||||||
startPendingCallCleanup,
|
startPendingCallCleanup,
|
||||||
|
|||||||
@@ -1,33 +1,19 @@
|
|||||||
export type CommentType = "line" | "block" | "docstring"
|
export type {
|
||||||
|
ApplyPatchAccumulator,
|
||||||
export interface CommentInfo {
|
ApplyPatchFileMetadata,
|
||||||
text: string
|
CheckResult,
|
||||||
lineNumber: number
|
CheckerEdit,
|
||||||
filePath: string
|
CommentFilter,
|
||||||
commentType: CommentType
|
CommentInfo,
|
||||||
isDocstring: boolean
|
CommentType,
|
||||||
metadata?: Record<string, string>
|
FileComments,
|
||||||
}
|
FilterResult,
|
||||||
|
HookInput,
|
||||||
export interface PendingCall {
|
PendingCall,
|
||||||
filePath: string
|
ResolveCommentCheckerBinaryInput,
|
||||||
content?: string
|
RunCommentCheckerInput,
|
||||||
oldString?: string
|
RunCommentCheckerOptions,
|
||||||
newString?: string
|
SpawnFn,
|
||||||
edits?: Array<{ old_string: string; new_string: string }>
|
SpawnProcess,
|
||||||
tool: "write" | "edit" | "multiedit"
|
SpawnSignal,
|
||||||
sessionID: string
|
} from "@oh-my-opencode/comment-checker-core"
|
||||||
timestamp: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileComments {
|
|
||||||
filePath: string
|
|
||||||
comments: CommentInfo[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FilterResult {
|
|
||||||
shouldSkip: boolean
|
|
||||||
reason?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CommentFilter = (comment: CommentInfo) => FilterResult
|
|
||||||
|
|||||||
Reference in New Issue
Block a user