chore: remove 1,152 lines of verified dead code (#874)
* chore(deps): remove unused dependencies Removed @openauthjs/openauth, hono, open, and xdg-basedir - none are imported in src/ Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * chore(cleanup): remove unused agent prompts and tool files Deleted: - src/agents/build-prompt.ts (exports never imported) - src/agents/plan-prompt.ts (exports never imported) - src/tools/ast-grep/napi.ts (never imported) - src/tools/interactive-bash/types.ts (never imported) Verified by: LSP FindReferences + explore agents Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * chore(hooks): remove unused comment-checker filters Deleted entire filters/ directory: - filters/bdd.ts - filters/directive.ts - filters/docstring.ts - filters/shebang.ts - filters/index.ts Not used by main hook (cli.ts uses external binary instead) Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * chore(hooks): remove unused comment-checker output and constants Deleted: - output/formatter.ts - output/xml-builder.ts - output/index.ts - constants.ts All 0 external imports - migrated to external binary Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * chore(hooks): remove unused pruning subsystem Deleted pruning subsystem (dependency order): - pruning-purge-errors.ts - pruning-storage.ts - pruning-supersede.ts - pruning-executor.ts Not imported by main recovery hook Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * chore(hooks): remove unused createBackgroundCompactionHook export Removed export from index.ts - never imported in src/index.ts Verified by: LSP FindReferences (only 2 refs: definition + barrel export) Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> --------- Co-authored-by: justsisyphus <sisyphus-dev-ai@users.noreply.github.com> Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,125 +0,0 @@
|
||||
import type { DynamicContextPruningConfig } from "../../config"
|
||||
import type { PruningState, PruningResult } from "./pruning-types"
|
||||
import { executeDeduplication } from "./pruning-deduplication"
|
||||
import { executeSupersedeWrites } from "./pruning-supersede"
|
||||
import { executePurgeErrors } from "./pruning-purge-errors"
|
||||
import { applyPruning } from "./pruning-storage"
|
||||
import { log } from "../../shared/logger"
|
||||
|
||||
const DEFAULT_PROTECTED_TOOLS = new Set([
|
||||
"task",
|
||||
"todowrite",
|
||||
"todoread",
|
||||
"lsp_rename",
|
||||
"session_read",
|
||||
"session_write",
|
||||
"session_search",
|
||||
])
|
||||
|
||||
function createPruningState(): PruningState {
|
||||
return {
|
||||
toolIdsToPrune: new Set<string>(),
|
||||
currentTurn: 0,
|
||||
fileOperations: new Map(),
|
||||
toolSignatures: new Map(),
|
||||
erroredTools: new Map(),
|
||||
}
|
||||
}
|
||||
|
||||
export async function executeDynamicContextPruning(
|
||||
sessionID: string,
|
||||
config: DynamicContextPruningConfig,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
client: any
|
||||
): Promise<PruningResult> {
|
||||
const state = createPruningState()
|
||||
|
||||
const protectedTools = new Set([
|
||||
...DEFAULT_PROTECTED_TOOLS,
|
||||
...(config.protected_tools || []),
|
||||
])
|
||||
|
||||
log("[pruning-executor] starting DCP", {
|
||||
sessionID,
|
||||
notification: config.notification,
|
||||
turnProtection: config.turn_protection,
|
||||
})
|
||||
|
||||
let dedupCount = 0
|
||||
let supersedeCount = 0
|
||||
let purgeCount = 0
|
||||
|
||||
if (config.strategies?.deduplication?.enabled !== false) {
|
||||
dedupCount = executeDeduplication(
|
||||
sessionID,
|
||||
state,
|
||||
{ enabled: true },
|
||||
protectedTools
|
||||
)
|
||||
}
|
||||
|
||||
if (config.strategies?.supersede_writes?.enabled !== false) {
|
||||
supersedeCount = executeSupersedeWrites(
|
||||
sessionID,
|
||||
state,
|
||||
{
|
||||
enabled: true,
|
||||
aggressive: config.strategies?.supersede_writes?.aggressive || false,
|
||||
},
|
||||
protectedTools
|
||||
)
|
||||
}
|
||||
|
||||
if (config.strategies?.purge_errors?.enabled !== false) {
|
||||
purgeCount = executePurgeErrors(
|
||||
sessionID,
|
||||
state,
|
||||
{
|
||||
enabled: true,
|
||||
turns: config.strategies?.purge_errors?.turns || 5,
|
||||
},
|
||||
protectedTools
|
||||
)
|
||||
}
|
||||
|
||||
const totalPruned = state.toolIdsToPrune.size
|
||||
const tokensSaved = await applyPruning(sessionID, state)
|
||||
|
||||
log("[pruning-executor] DCP complete", {
|
||||
totalPruned,
|
||||
tokensSaved,
|
||||
deduplication: dedupCount,
|
||||
supersede: supersedeCount,
|
||||
purge: purgeCount,
|
||||
})
|
||||
|
||||
const result: PruningResult = {
|
||||
itemsPruned: totalPruned,
|
||||
totalTokensSaved: tokensSaved,
|
||||
strategies: {
|
||||
deduplication: dedupCount,
|
||||
supersedeWrites: supersedeCount,
|
||||
purgeErrors: purgeCount,
|
||||
},
|
||||
}
|
||||
|
||||
if (config.notification !== "off" && totalPruned > 0) {
|
||||
const message =
|
||||
config.notification === "detailed"
|
||||
? `Pruned ${totalPruned} tool outputs (~${Math.round(tokensSaved / 1000)}k tokens). Dedup: ${dedupCount}, Supersede: ${supersedeCount}, Purge: ${purgeCount}`
|
||||
: `Pruned ${totalPruned} tool outputs (~${Math.round(tokensSaved / 1000)}k tokens)`
|
||||
|
||||
await client.tui
|
||||
.showToast({
|
||||
body: {
|
||||
title: "Dynamic Context Pruning",
|
||||
message,
|
||||
variant: "success",
|
||||
duration: 3000,
|
||||
},
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import { existsSync, readdirSync, readFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import type { PruningState, ErroredToolCall } from "./pruning-types"
|
||||
import { estimateTokens } from "./pruning-types"
|
||||
import { log } from "../../shared/logger"
|
||||
import { MESSAGE_STORAGE } from "../../features/hook-message-injector"
|
||||
|
||||
export interface PurgeErrorsConfig {
|
||||
enabled: boolean
|
||||
turns: number
|
||||
protectedTools?: string[]
|
||||
}
|
||||
|
||||
interface ToolPart {
|
||||
type: string
|
||||
callID?: string
|
||||
tool?: string
|
||||
state?: {
|
||||
input?: unknown
|
||||
output?: string
|
||||
status?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface MessagePart {
|
||||
type: string
|
||||
parts?: ToolPart[]
|
||||
}
|
||||
|
||||
function getMessageDir(sessionID: string): string | null {
|
||||
if (!existsSync(MESSAGE_STORAGE)) return null
|
||||
|
||||
const directPath = join(MESSAGE_STORAGE, sessionID)
|
||||
if (existsSync(directPath)) return directPath
|
||||
|
||||
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
||||
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
|
||||
if (existsSync(sessionPath)) return sessionPath
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function readMessages(sessionID: string): MessagePart[] {
|
||||
const messageDir = getMessageDir(sessionID)
|
||||
if (!messageDir) return []
|
||||
|
||||
const messages: MessagePart[] = []
|
||||
|
||||
try {
|
||||
const files = readdirSync(messageDir).filter(f => f.endsWith(".json"))
|
||||
for (const file of files) {
|
||||
const content = readFileSync(join(messageDir, file), "utf-8")
|
||||
const data = JSON.parse(content)
|
||||
if (data.parts) {
|
||||
messages.push(data)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
export function executePurgeErrors(
|
||||
sessionID: string,
|
||||
state: PruningState,
|
||||
config: PurgeErrorsConfig,
|
||||
protectedTools: Set<string>
|
||||
): number {
|
||||
if (!config.enabled) return 0
|
||||
|
||||
const messages = readMessages(sessionID)
|
||||
|
||||
let currentTurn = 0
|
||||
|
||||
for (const msg of messages) {
|
||||
if (!msg.parts) continue
|
||||
|
||||
for (const part of msg.parts) {
|
||||
if (part.type === "step-start") {
|
||||
currentTurn++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.currentTurn = currentTurn
|
||||
|
||||
let turnCounter = 0
|
||||
let prunedCount = 0
|
||||
let tokensSaved = 0
|
||||
|
||||
for (const msg of messages) {
|
||||
if (!msg.parts) continue
|
||||
|
||||
for (const part of msg.parts) {
|
||||
if (part.type === "step-start") {
|
||||
turnCounter++
|
||||
continue
|
||||
}
|
||||
|
||||
if (part.type !== "tool" || !part.callID || !part.tool) continue
|
||||
|
||||
if (protectedTools.has(part.tool)) continue
|
||||
|
||||
if (config.protectedTools?.includes(part.tool)) continue
|
||||
|
||||
if (state.toolIdsToPrune.has(part.callID)) continue
|
||||
|
||||
if (part.state?.status !== "error") continue
|
||||
|
||||
const turnAge = currentTurn - turnCounter
|
||||
|
||||
if (turnAge >= config.turns) {
|
||||
state.toolIdsToPrune.add(part.callID)
|
||||
prunedCount++
|
||||
|
||||
const input = part.state.input
|
||||
if (input) {
|
||||
tokensSaved += estimateTokens(JSON.stringify(input))
|
||||
}
|
||||
|
||||
const errorInfo: ErroredToolCall = {
|
||||
callID: part.callID,
|
||||
toolName: part.tool,
|
||||
turn: turnCounter,
|
||||
errorAge: turnAge,
|
||||
}
|
||||
|
||||
state.erroredTools.set(part.callID, errorInfo)
|
||||
|
||||
log("[pruning-purge-errors] pruned old error", {
|
||||
tool: part.tool,
|
||||
callID: part.callID,
|
||||
turn: turnCounter,
|
||||
errorAge: turnAge,
|
||||
threshold: config.turns,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("[pruning-purge-errors] complete", {
|
||||
prunedCount,
|
||||
tokensSaved,
|
||||
currentTurn,
|
||||
threshold: config.turns,
|
||||
})
|
||||
|
||||
return prunedCount
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import type { PruningState } from "./pruning-types"
|
||||
import { estimateTokens } from "./pruning-types"
|
||||
import { log } from "../../shared/logger"
|
||||
import { MESSAGE_STORAGE } from "../../features/hook-message-injector"
|
||||
|
||||
function getMessageDir(sessionID: string): string | null {
|
||||
if (!existsSync(MESSAGE_STORAGE)) return null
|
||||
|
||||
const directPath = join(MESSAGE_STORAGE, sessionID)
|
||||
if (existsSync(directPath)) return directPath
|
||||
|
||||
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
||||
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
|
||||
if (existsSync(sessionPath)) return sessionPath
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
interface ToolPart {
|
||||
type: string
|
||||
callID?: string
|
||||
tool?: string
|
||||
state?: {
|
||||
input?: unknown
|
||||
output?: string
|
||||
status?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface MessageData {
|
||||
parts?: ToolPart[]
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export async function applyPruning(
|
||||
sessionID: string,
|
||||
state: PruningState
|
||||
): Promise<number> {
|
||||
const messageDir = getMessageDir(sessionID)
|
||||
if (!messageDir) {
|
||||
log("[pruning-storage] message dir not found", { sessionID })
|
||||
return 0
|
||||
}
|
||||
|
||||
let totalTokensSaved = 0
|
||||
let filesModified = 0
|
||||
|
||||
try {
|
||||
const files = readdirSync(messageDir).filter(f => f.endsWith(".json"))
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = join(messageDir, file)
|
||||
const content = readFileSync(filePath, "utf-8")
|
||||
const data: MessageData = JSON.parse(content)
|
||||
|
||||
if (!data.parts) continue
|
||||
|
||||
let modified = false
|
||||
|
||||
for (const part of data.parts) {
|
||||
if (part.type !== "tool" || !part.callID) continue
|
||||
|
||||
if (!state.toolIdsToPrune.has(part.callID)) continue
|
||||
|
||||
if (part.state?.input) {
|
||||
const inputStr = JSON.stringify(part.state.input)
|
||||
totalTokensSaved += estimateTokens(inputStr)
|
||||
part.state.input = { __pruned: true, reason: "DCP" }
|
||||
modified = true
|
||||
}
|
||||
|
||||
if (part.state?.output) {
|
||||
totalTokensSaved += estimateTokens(part.state.output)
|
||||
part.state.output = "[Content pruned by Dynamic Context Pruning]"
|
||||
modified = true
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8")
|
||||
filesModified++
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log("[pruning-storage] error applying pruning", {
|
||||
sessionID,
|
||||
error: String(error),
|
||||
})
|
||||
}
|
||||
|
||||
log("[pruning-storage] applied pruning", {
|
||||
sessionID,
|
||||
filesModified,
|
||||
totalTokensSaved,
|
||||
})
|
||||
|
||||
return totalTokensSaved
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
import { existsSync, readdirSync, readFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import type { PruningState, FileOperation } from "./pruning-types"
|
||||
import { estimateTokens } from "./pruning-types"
|
||||
import { log } from "../../shared/logger"
|
||||
import { MESSAGE_STORAGE } from "../../features/hook-message-injector"
|
||||
|
||||
export interface SupersedeWritesConfig {
|
||||
enabled: boolean
|
||||
aggressive: boolean
|
||||
}
|
||||
|
||||
interface ToolPart {
|
||||
type: string
|
||||
callID?: string
|
||||
tool?: string
|
||||
state?: {
|
||||
input?: unknown
|
||||
output?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface MessagePart {
|
||||
type: string
|
||||
parts?: ToolPart[]
|
||||
}
|
||||
|
||||
function getMessageDir(sessionID: string): string | null {
|
||||
if (!existsSync(MESSAGE_STORAGE)) return null
|
||||
|
||||
const directPath = join(MESSAGE_STORAGE, sessionID)
|
||||
if (existsSync(directPath)) return directPath
|
||||
|
||||
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
||||
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
|
||||
if (existsSync(sessionPath)) return sessionPath
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function readMessages(sessionID: string): MessagePart[] {
|
||||
const messageDir = getMessageDir(sessionID)
|
||||
if (!messageDir) return []
|
||||
|
||||
const messages: MessagePart[] = []
|
||||
|
||||
try {
|
||||
const files = readdirSync(messageDir).filter(f => f.endsWith(".json"))
|
||||
for (const file of files) {
|
||||
const content = readFileSync(join(messageDir, file), "utf-8")
|
||||
const data = JSON.parse(content)
|
||||
if (data.parts) {
|
||||
messages.push(data)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
function extractFilePath(toolName: string, input: unknown): string | null {
|
||||
if (!input || typeof input !== "object") return null
|
||||
|
||||
const inputObj = input as Record<string, unknown>
|
||||
|
||||
if (toolName === "write" || toolName === "edit" || toolName === "read") {
|
||||
if (typeof inputObj.filePath === "string") {
|
||||
return inputObj.filePath
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function executeSupersedeWrites(
|
||||
sessionID: string,
|
||||
state: PruningState,
|
||||
config: SupersedeWritesConfig,
|
||||
protectedTools: Set<string>
|
||||
): number {
|
||||
if (!config.enabled) return 0
|
||||
|
||||
const messages = readMessages(sessionID)
|
||||
const writesByFile = new Map<string, FileOperation[]>()
|
||||
const readsByFile = new Map<string, number[]>()
|
||||
|
||||
let currentTurn = 0
|
||||
|
||||
for (const msg of messages) {
|
||||
if (!msg.parts) continue
|
||||
|
||||
for (const part of msg.parts) {
|
||||
if (part.type === "step-start") {
|
||||
currentTurn++
|
||||
continue
|
||||
}
|
||||
|
||||
if (part.type !== "tool" || !part.callID || !part.tool) continue
|
||||
|
||||
if (protectedTools.has(part.tool)) continue
|
||||
|
||||
if (state.toolIdsToPrune.has(part.callID)) continue
|
||||
|
||||
const filePath = extractFilePath(part.tool, part.state?.input)
|
||||
if (!filePath) continue
|
||||
|
||||
if (part.tool === "write" || part.tool === "edit") {
|
||||
if (!writesByFile.has(filePath)) {
|
||||
writesByFile.set(filePath, [])
|
||||
}
|
||||
writesByFile.get(filePath)!.push({
|
||||
callID: part.callID,
|
||||
tool: part.tool,
|
||||
filePath,
|
||||
turn: currentTurn,
|
||||
})
|
||||
|
||||
if (!state.fileOperations.has(filePath)) {
|
||||
state.fileOperations.set(filePath, [])
|
||||
}
|
||||
state.fileOperations.get(filePath)!.push({
|
||||
callID: part.callID,
|
||||
tool: part.tool,
|
||||
filePath,
|
||||
turn: currentTurn,
|
||||
})
|
||||
} else if (part.tool === "read") {
|
||||
if (!readsByFile.has(filePath)) {
|
||||
readsByFile.set(filePath, [])
|
||||
}
|
||||
readsByFile.get(filePath)!.push(currentTurn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let prunedCount = 0
|
||||
let tokensSaved = 0
|
||||
|
||||
for (const [filePath, writes] of writesByFile) {
|
||||
const reads = readsByFile.get(filePath) || []
|
||||
|
||||
if (config.aggressive) {
|
||||
for (const write of writes) {
|
||||
const superseded = reads.some(readTurn => readTurn > write.turn)
|
||||
if (superseded) {
|
||||
state.toolIdsToPrune.add(write.callID)
|
||||
prunedCount++
|
||||
|
||||
const input = findToolInput(messages, write.callID)
|
||||
if (input) {
|
||||
tokensSaved += estimateTokens(JSON.stringify(input))
|
||||
}
|
||||
|
||||
log("[pruning-supersede] pruned superseded write", {
|
||||
tool: write.tool,
|
||||
callID: write.callID,
|
||||
turn: write.turn,
|
||||
filePath,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (writes.length > 1) {
|
||||
for (const write of writes.slice(0, -1)) {
|
||||
const superseded = reads.some(readTurn => readTurn > write.turn)
|
||||
if (superseded) {
|
||||
state.toolIdsToPrune.add(write.callID)
|
||||
prunedCount++
|
||||
|
||||
const input = findToolInput(messages, write.callID)
|
||||
if (input) {
|
||||
tokensSaved += estimateTokens(JSON.stringify(input))
|
||||
}
|
||||
|
||||
log("[pruning-supersede] pruned superseded write (conservative)", {
|
||||
tool: write.tool,
|
||||
callID: write.callID,
|
||||
turn: write.turn,
|
||||
filePath,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("[pruning-supersede] complete", {
|
||||
prunedCount,
|
||||
tokensSaved,
|
||||
filesTracked: writesByFile.size,
|
||||
mode: config.aggressive ? "aggressive" : "conservative",
|
||||
})
|
||||
|
||||
return prunedCount
|
||||
}
|
||||
|
||||
function findToolInput(messages: MessagePart[], callID: string): unknown | null {
|
||||
for (const msg of messages) {
|
||||
if (!msg.parts) continue
|
||||
|
||||
for (const part of msg.parts) {
|
||||
if (part.type === "tool" && part.callID === callID && part.state?.input) {
|
||||
return part.state.input
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
export const BDD_KEYWORDS = new Set([
|
||||
"given",
|
||||
"when",
|
||||
"then",
|
||||
"arrange",
|
||||
"act",
|
||||
"assert",
|
||||
"when & then",
|
||||
"when&then",
|
||||
])
|
||||
|
||||
export const TYPE_CHECKER_PREFIXES = [
|
||||
"type:",
|
||||
"noqa",
|
||||
"pyright:",
|
||||
"ruff:",
|
||||
"mypy:",
|
||||
"pylint:",
|
||||
"flake8:",
|
||||
"pyre:",
|
||||
"pytype:",
|
||||
"eslint-disable",
|
||||
"eslint-enable",
|
||||
"eslint-ignore",
|
||||
"prettier-ignore",
|
||||
"ts-ignore",
|
||||
"ts-expect-error",
|
||||
"ts-nocheck",
|
||||
"clippy::",
|
||||
"allow(",
|
||||
"deny(",
|
||||
"warn(",
|
||||
"forbid(",
|
||||
"nolint",
|
||||
"go:generate",
|
||||
"go:build",
|
||||
"go:embed",
|
||||
"coverage:",
|
||||
"c8 ignore",
|
||||
"biome-ignore",
|
||||
"region",
|
||||
"endregion",
|
||||
]
|
||||
|
||||
export const HOOK_MESSAGE_HEADER = `COMMENT/DOCSTRING DETECTED - IMMEDIATE ACTION REQUIRED
|
||||
|
||||
Your recent changes contain comments or docstrings, which triggered this hook.
|
||||
You need to take immediate action. You must follow the conditions below.
|
||||
(Listed in priority order - you must always act according to this priority order)
|
||||
|
||||
CRITICAL WARNING: This hook message MUST NEVER be ignored, even if you receive it multiple times.
|
||||
You MUST provide corresponding explanation or action for EACH occurrence of this message.
|
||||
Ignoring this message or failing to respond appropriately is strictly prohibited.
|
||||
|
||||
PRIORITY-BASED ACTION GUIDELINES:
|
||||
|
||||
1. This is a comment/docstring that already existed before
|
||||
\t-> Explain to the user that this is an existing comment/docstring and proceed (justify it)
|
||||
|
||||
2. This is a newly written comment: but it's in given, when, then format
|
||||
\t-> Tell the user it's a BDD comment and proceed (justify it)
|
||||
\t-> Note: This applies to comments only, not docstrings
|
||||
|
||||
3. This is a newly written comment/docstring: but it's a necessary comment/docstring
|
||||
\t-> Tell the user why this comment/docstring is absolutely necessary and proceed (justify it)
|
||||
\t-> Examples of necessary comments: complex algorithms, security-related, performance optimization, regex, mathematical formulas
|
||||
\t-> Examples of necessary docstrings: public API documentation, complex module/class interfaces
|
||||
\t-> IMPORTANT: Most docstrings are unnecessary if the code is self-explanatory. Only keep truly essential ones.
|
||||
|
||||
4. This is a newly written comment/docstring: but it's an unnecessary comment/docstring
|
||||
\t-> Apologize to the user and remove the comment/docstring.
|
||||
\t-> Make the code itself clearer so it can be understood without comments/docstrings.
|
||||
\t-> For verbose docstrings: refactor code to be self-documenting instead of adding lengthy explanations.
|
||||
|
||||
CODE SMELL WARNING: Using comments as visual separators (e.g., "// =========", "# ---", "// *** Section ***")
|
||||
is a code smell. If you need separators, your file is too long or poorly organized.
|
||||
Refactor into smaller modules or use proper code organization instead of comment-based section dividers.
|
||||
|
||||
MANDATORY REQUIREMENT: You must acknowledge this hook message and take one of the above actions.
|
||||
Review in the above priority order and take the corresponding action EVERY TIME this appears.
|
||||
|
||||
Detected comments/docstrings:
|
||||
`
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { CommentInfo, FilterResult } from "../types"
|
||||
import { BDD_KEYWORDS } from "../constants"
|
||||
|
||||
function stripCommentPrefix(text: string): string {
|
||||
let stripped = text.trim().toLowerCase()
|
||||
const prefixes = ["#", "//", "--", "/*", "*/"]
|
||||
for (const prefix of prefixes) {
|
||||
if (stripped.startsWith(prefix)) {
|
||||
stripped = stripped.slice(prefix.length).trim()
|
||||
}
|
||||
}
|
||||
return stripped
|
||||
}
|
||||
|
||||
export function filterBddComments(comment: CommentInfo): FilterResult {
|
||||
const normalized = stripCommentPrefix(comment.text)
|
||||
if (BDD_KEYWORDS.has(normalized)) {
|
||||
return { shouldSkip: true, reason: `BDD keyword: ${normalized}` }
|
||||
}
|
||||
return { shouldSkip: false }
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { CommentInfo, FilterResult } from "../types"
|
||||
import { TYPE_CHECKER_PREFIXES } from "../constants"
|
||||
|
||||
function stripCommentPrefix(text: string): string {
|
||||
let stripped = text.trim().toLowerCase()
|
||||
const prefixes = ["#", "//", "/*", "--"]
|
||||
for (const prefix of prefixes) {
|
||||
if (stripped.startsWith(prefix)) {
|
||||
stripped = stripped.slice(prefix.length).trim()
|
||||
}
|
||||
}
|
||||
stripped = stripped.replace(/^@/, "")
|
||||
return stripped
|
||||
}
|
||||
|
||||
export function filterDirectiveComments(comment: CommentInfo): FilterResult {
|
||||
const normalized = stripCommentPrefix(comment.text)
|
||||
for (const prefix of TYPE_CHECKER_PREFIXES) {
|
||||
if (normalized.startsWith(prefix.toLowerCase())) {
|
||||
return { shouldSkip: true, reason: `Directive: ${prefix}` }
|
||||
}
|
||||
}
|
||||
return { shouldSkip: false }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { CommentInfo, FilterResult } from "../types"
|
||||
|
||||
export function filterDocstringComments(comment: CommentInfo): FilterResult {
|
||||
if (comment.isDocstring) {
|
||||
return { shouldSkip: true, reason: "Docstring" }
|
||||
}
|
||||
const trimmed = comment.text.trimStart()
|
||||
if (trimmed.startsWith("/**")) {
|
||||
return { shouldSkip: true, reason: "JSDoc/PHPDoc" }
|
||||
}
|
||||
return { shouldSkip: false }
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { CommentInfo, CommentFilter } from "../types"
|
||||
import { filterBddComments } from "./bdd"
|
||||
import { filterDirectiveComments } from "./directive"
|
||||
import { filterDocstringComments } from "./docstring"
|
||||
import { filterShebangComments } from "./shebang"
|
||||
|
||||
export { filterBddComments, filterDirectiveComments, filterDocstringComments, filterShebangComments }
|
||||
|
||||
const ALL_FILTERS: CommentFilter[] = [
|
||||
filterShebangComments,
|
||||
filterBddComments,
|
||||
filterDirectiveComments,
|
||||
filterDocstringComments,
|
||||
]
|
||||
|
||||
export function applyFilters(comments: CommentInfo[]): CommentInfo[] {
|
||||
return comments.filter((comment) => {
|
||||
for (const filter of ALL_FILTERS) {
|
||||
const result = filter(comment)
|
||||
if (result.shouldSkip) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { CommentInfo, FilterResult } from "../types"
|
||||
|
||||
export function filterShebangComments(comment: CommentInfo): FilterResult {
|
||||
const trimmed = comment.text.trimStart()
|
||||
if (trimmed.startsWith("#!")) {
|
||||
return { shouldSkip: true, reason: "Shebang" }
|
||||
}
|
||||
return { shouldSkip: false }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { FileComments } from "../types"
|
||||
import { HOOK_MESSAGE_HEADER } from "../constants"
|
||||
import { buildCommentsXml } from "./xml-builder"
|
||||
|
||||
export function formatHookMessage(fileCommentsList: FileComments[]): string {
|
||||
if (fileCommentsList.length === 0) {
|
||||
return ""
|
||||
}
|
||||
const xml = buildCommentsXml(fileCommentsList)
|
||||
return `${HOOK_MESSAGE_HEADER}${xml}\n`
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { buildCommentsXml } from "./xml-builder"
|
||||
export { formatHookMessage } from "./formatter"
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { FileComments } from "../types"
|
||||
|
||||
function escapeXml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
}
|
||||
|
||||
export function buildCommentsXml(fileCommentsList: FileComments[]): string {
|
||||
const lines: string[] = []
|
||||
|
||||
for (const fc of fileCommentsList) {
|
||||
lines.push(`<comments file="${escapeXml(fc.filePath)}">`)
|
||||
for (const comment of fc.comments) {
|
||||
lines.push(`\t<comment line-number="${comment.lineNumber}">${escapeXml(comment.text)}</comment>`)
|
||||
}
|
||||
lines.push(`</comments>`)
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
}
|
||||
@@ -14,7 +14,6 @@ export { createThinkModeHook } from "./think-mode";
|
||||
export { createClaudeCodeHooksHook } from "./claude-code-hooks";
|
||||
export { createRulesInjectorHook } from "./rules-injector";
|
||||
export { createBackgroundNotificationHook } from "./background-notification"
|
||||
export { createBackgroundCompactionHook } from "./background-compaction"
|
||||
export { createAutoUpdateCheckerHook } from "./auto-update-checker";
|
||||
|
||||
export { createAgentUsageReminderHook } from "./agent-usage-reminder";
|
||||
|
||||
Reference in New Issue
Block a user