test(shared): add audit test forbidding direct output.args mutation + fix 9th site

Add replace-tool-args.audit.test.ts that scans src/**/*.ts for direct
output.args property assignments and Object.assign(output.args, ...) outside
the helper. Also fix the 9th mutation site discovered by the audit in
compaction-todo-preserver/hook.ts.

Add replace-tool-args.test.ts with 12 regression tests covering both mutable
and Object.freeze'd output.args scenarios for all hook patterns.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-05-18 12:44:27 +09:00
parent 13d8857415
commit 3a63a8b205
3 changed files with 210 additions and 1 deletions
@@ -0,0 +1,60 @@
import { describe, it, expect } from "bun:test"
import { readdir, readFile } from "node:fs/promises"
import { join, relative } from "node:path"
const SRC_DIR = join(import.meta.dir, "..")
async function collectTsFiles(dir: string): Promise<string[]> {
const results: string[] = []
const entries = await readdir(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = join(dir, entry.name)
if (entry.isDirectory()) {
if (entry.name === "node_modules" || entry.name === "dist") continue
results.push(...(await collectTsFiles(fullPath)))
} else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".audit.test.ts")) {
results.push(fullPath)
}
}
return results
}
const HELPER_FILE = "shared/replace-tool-args.ts"
const DIRECT_MUTATION_PATTERN = /output\.args\.\w+\s*=[^=]/g
const OBJECT_ASSIGN_PATTERN = /Object\.assign\(\s*output\.args/g
describe("replace-tool-args audit", () => {
it("#given src/**/*.ts files #when scanning for direct output.args mutation #then no matches found outside the helper", async () => {
// given
const files = await collectTsFiles(SRC_DIR)
const violations: string[] = []
// when
for (const file of files) {
const relPath = relative(SRC_DIR, file)
if (relPath === HELPER_FILE) continue
const content = await readFile(file, "utf-8")
const lines = content.split("\n")
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (DIRECT_MUTATION_PATTERN.test(line)) {
violations.push(`${relPath}:${i + 1}: ${line.trim()}`)
}
DIRECT_MUTATION_PATTERN.lastIndex = 0
if (OBJECT_ASSIGN_PATTERN.test(line)) {
violations.push(`${relPath}:${i + 1}: ${line.trim()}`)
}
OBJECT_ASSIGN_PATTERN.lastIndex = 0
}
}
// then
expect(violations).toEqual([])
})
})