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
9 changes: 9 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[shell_environment_policy]
inherit = "core"

[shell_environment_policy.set]
GIT_TERMINAL_PROMPT = "0"
GIT_EDITOR = "true"
GIT_PAGER = "cat"
PAGER = "cat"
GIT_SSH_COMMAND = "ssh -oBatchMode=yes"
43 changes: 43 additions & 0 deletions .codex/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "bun 'C:\\Users\\Jk101\\Desktop\\jgengine\\.codex\\hooks\\repetitive-edit-watch.mjs'"
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bun 'C:\\Users\\Jk101\\Desktop\\jgengine\\.codex\\hooks\\session-start.mjs'"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bun 'C:\\Users\\Jk101\\Desktop\\jgengine\\.codex\\hooks\\warn-unpushed.mjs'"
}
]
},
{
"hooks": [
{
"type": "command",
"command": "bun 'C:\\Users\\Jk101\\Desktop\\jgengine\\.codex\\hooks\\papercut-reminder.mjs'"
}
]
}
]
}
}
152 changes: 152 additions & 0 deletions .codex/hooks/papercut-reminder.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { readFileSync, existsSync } from "node:fs";
import { homedir } from "node:os";
import path from "node:path";

let input = {};
try {
input = JSON.parse(readFileSync(0, "utf8"));
} catch {
/* no stdin — treat as empty */
}
if (input.stop_hook_active) process.exit(0);

function resolveTranscriptPath() {
if (typeof input.transcript_path === "string" && existsSync(input.transcript_path)) {
return input.transcript_path;
}
if (typeof input.session_id !== "string") return null;
const cwd = typeof input.cwd === "string" ? input.cwd : process.cwd();
const sanitized = cwd.replace(/[/\\]/g, "-");
const guess = path.join(homedir(), ".claude", "projects", sanitized, `${input.session_id}.jsonl`);
return existsSync(guess) ? guess : null;
}

const transcriptPath = resolveTranscriptPath();
if (transcriptPath === null) process.exit(0);

let entries;
try {
entries = readFileSync(transcriptPath, "utf8")
.split("\n")
.filter(Boolean)
.map((line) => {
try {
return JSON.parse(line);
} catch {
return null;
}
})
.filter((entry) => entry !== null);
} catch {
process.exit(0);
}

function messageContent(entry) {
const content = entry?.message?.content;
return Array.isArray(content) ? content : [];
}

// Already logged a papercut this session — nothing to remind about.
const alreadyLogged = entries.some((entry) =>
messageContent(entry).some(
(block) =>
block.type === "tool_use" &&
block.name === "Bash" &&
typeof block.input?.command === "string" &&
/\bpapercut\b/i.test(block.input.command),
),
);
if (alreadyLogged) process.exit(0);

const toolUses = [];
const resultById = new Map();
for (const entry of entries) {
for (const block of messageContent(entry)) {
if (block.type === "tool_use") toolUses.push(block);
if (block.type === "tool_result") resultById.set(block.tool_use_id, block);
}
}

function isError(toolUse) {
return resultById.get(toolUse.id)?.is_error === true;
}

// Signal A: the exact same non-trivial Bash command was run twice in a row
// with no Edit/Write/NotebookEdit between the two runs, and at least one run
// failed — a genuine dead-end retry. Requiring "no edit in between" is what
// excludes a normal fix-test-fix loop (there, the same test command reruns
// on purpose after code changed, and that's fine).
const EDIT_TOOLS = new Set(["Edit", "Write", "NotebookEdit"]);
const commandPositions = new Map();
toolUses.forEach((toolUse, index) => {
if (toolUse.name !== "Bash") return;
const command = (toolUse.input?.command ?? "").trim();
if (command.length < 8) return;
const positions = commandPositions.get(command) ?? [];
positions.push(index);
commandPositions.set(command, positions);
});

let retriedFailedCommand = null;
outerRetry: for (const [command, positions] of commandPositions) {
for (let k = 0; k < positions.length - 1; k += 1) {
const [from, to] = [positions[k], positions[k + 1]];
const editedBetween = toolUses.slice(from + 1, to).some((toolUse) => EDIT_TOOLS.has(toolUse.name));
if (editedBetween) continue;
if (isError(toolUses[from]) || isError(toolUses[to])) {
retriedFailedCommand = command;
break outerRetry;
}
}
}

// Signal B: a later Agent/Task call whose prompt heavily overlaps an earlier
// one's and explicitly corrects it (e.g. "run synchronously", "don't
// background") — the shape of a relaunch after an unusable first result.
const RELAUNCH_HINTS =
/(instead of backgrounding|re-?launch|try (that |it )?again|unusable (first )?result|(last|previous) (attempt|run|worker) (failed|returned nothing|stalled))/i;

function wordSet(text) {
return new Set((text ?? "").toLowerCase().match(/[a-z]{4,}/g) ?? []);
}
function overlapRatio(a, b) {
if (a.size === 0 || b.size === 0) return 0;
let shared = 0;
for (const word of a) if (b.has(word)) shared += 1;
return shared / Math.min(a.size, b.size);
}

const agentCalls = toolUses.filter((toolUse) => toolUse.name === "Agent" || toolUse.name === "Task");
let relaunchedAgent = null;
outer: for (let i = 0; i < agentCalls.length; i += 1) {
for (let j = i + 1; j < agentCalls.length; j += 1) {
const promptA = agentCalls[i].input?.prompt ?? "";
const promptB = agentCalls[j].input?.prompt ?? "";
if (
overlapRatio(wordSet(promptA), wordSet(promptB)) > 0.5 &&
RELAUNCH_HINTS.test(promptB) &&
!RELAUNCH_HINTS.test(promptA)
) {
relaunchedAgent = agentCalls[j].input?.description ?? "a subagent";
break outer;
}
}
}

if (retriedFailedCommand === null && relaunchedAgent === null) process.exit(0);

const bits = [];
if (retriedFailedCommand !== null) {
bits.push(`a Bash command was retried after failing: \`${retriedFailedCommand.slice(0, 120)}\``);
}
if (relaunchedAgent !== null) {
bits.push(`a subagent ("${relaunchedAgent}") looks like it was relaunched after an unusable first result`);
}

const reason =
`This session hit friction worth logging as a papercut: ${bits.join("; ")}.\n\n` +
`Per CLAUDE.md, log it now before stopping (proactively, no need to ask first):\n` +
` bun run papercut -m <your-model-id> "what you were doing → what got in the way"`;

process.stdout.write(JSON.stringify({ decision: "block", reason }));
process.exit(0);
28 changes: 28 additions & 0 deletions .codex/hooks/repetitive-edit-watch.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { appendFileSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

const input = JSON.parse(readFileSync(0, "utf8"));
const { session_id, tool_input } = input;
if (!tool_input?.old_string || !tool_input?.new_string) process.exit(0);

const key = JSON.stringify([
tool_input.old_string.slice(0, 200),
tool_input.new_string.slice(0, 200),
]);
const logPath = join(tmpdir(), `jg-edit-watch-${session_id}.jsonl`);
appendFileSync(logPath, JSON.stringify({ key, file: tool_input.file_path }) + "\n");

const lines = readFileSync(logPath, "utf8").trim().split("\n").map((l) => JSON.parse(l));
const files = new Set(lines.filter((l) => l.key === key).map((l) => l.file));

if (files.size >= 3) {
console.log(
JSON.stringify({
hookSpecificOutput: {
hookEventName: "PostToolUse",
additionalContext: `You have now applied the same edit manually in ${files.size} files. Stop hand-editing: build one regex find-and-replace instead — dry-run with rg '<pattern>', then rg -l '<pattern>' | xargs sed -i -E 's/<pattern>/<replacement>/g' (or perl -pi -e for multiline), then git diff --stat to confirm scope. See CLAUDE.md → Style.`,
},
}),
);
}
146 changes: 146 additions & 0 deletions .codex/hooks/session-start.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { execFileSync } from "node:child_process";

const git = (...args) => {
try {
return execFileSync("git", args, { stdio: ["ignore", "pipe", "ignore"], timeout: 30000 })
.toString()
.trim();
} catch {
return null;
}
};

const gitIn = (input, ...args) => {
try {
return execFileSync("git", args, { input, stdio: ["pipe", "pipe", "ignore"], timeout: 30000 })
.toString()
.trim();
} catch {
return null;
}
};

const patchIds = (diffText) => {
if (!diffText) return [];
const out = gitIn(diffText, "patch-id", "--stable");
if (!out) return [];
return out
.split("\n")
.map((line) => line.trim().split(/\s+/)[0])
.filter(Boolean);
};

const contentAlreadyUpstream = (remoteRef, branch) => {
const base = git("merge-base", remoteRef, branch);
if (!base) return false;
const upstream = new Set(patchIds(git("log", "-p", "--no-color", `${base}..${remoteRef}`)));
if (upstream.size === 0) return false;
const combined = patchIds(git("diff", "--no-color", base, branch))[0];
if (combined && upstream.has(combined)) return true;
const local = patchIds(git("log", "-p", "--no-color", `${remoteRef}..${branch}`));
return local.length > 0 && local.every((id) => upstream.has(id));
};

const emit = (context) => {
process.stdout.write(
JSON.stringify({
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: context },
}),
);
process.exit(0);
};

const gitDir = git("rev-parse", "--absolute-git-dir");
if (!gitDir) process.exit(0);

const unshallow = () => {
if (git("rev-parse", "--is-shallow-repository") !== "true") return "";
try {
execFileSync("git", ["fetch", "--unshallow", "--quiet", "origin"], {
stdio: "ignore",
timeout: 180000,
});
return "";
} catch {
return (
`\n\n⚠️ This clone is SHALLOW and un-shallowing failed (network/timeout). History ` +
`comparisons against origin are unreliable — run git fetch --unshallow origin before ` +
`diagnosing any branch state.`
);
}
};
const shallowNote = unshallow();

git("fetch", "origin", "--prune", "--quiet");

const branch = git("rev-parse", "--abbrev-ref", "HEAD") ?? "?";
const defaultBranch =
(git("symbolic-ref", "--quiet", "refs/remotes/origin/HEAD") ?? "").split("/").pop() || "main";
const remoteMain = `origin/${defaultBranch}`;
const dirtyTracked = git("status", "--porcelain", "--untracked-files=no");
const notes = [];

if (branch === defaultBranch) {
if (!dirtyTracked && git("merge", "--ff-only", remoteMain) !== null) {
notes.push(`Fast-forwarded ${defaultBranch} to ${remoteMain}.`);
}
} else if (branch !== "HEAD") {
const remoteBranch = `origin/${branch}`;
const hasRemoteBranch = git("rev-parse", "--verify", "--quiet", remoteBranch) !== null;

if (hasRemoteBranch && !dirtyTracked) {
const counts = git("rev-list", "--left-right", "--count", `${branch}...${remoteBranch}`);
const [ahead, behind] = (counts ?? "0\t0").split(/\s+/).map(Number);
if (behind > 0 && ahead === 0 && git("merge", "--ff-only", remoteBranch) !== null) {
notes.push(`Fast-forwarded ${branch} to ${remoteBranch}.`);
}
}

const unpushed = Number(git("rev-list", "--count", "HEAD", "--not", "--remotes")) || 0;
const headSha = git("rev-parse", "HEAD");
const mainSha = git("rev-parse", remoteMain);
const behindMain =
headSha !== mainSha && git("merge-base", "--is-ancestor", "HEAD", remoteMain) === "";
if (!dirtyTracked && behindMain) {
if (git("reset", "--hard", remoteMain) !== null) {
notes.push(
`Self-healed: "${branch}" pointed at history already contained in ${remoteMain} — ` +
`restarted it from ${remoteMain}. Start committing on top of this fresh base.`,
);
}
} else if (!dirtyTracked && contentAlreadyUpstream(remoteMain, branch)) {
if (git("reset", "--hard", remoteMain) !== null) {
notes.push(
`Self-healed: every commit on "${branch}" was already squash-merged into ${remoteMain} ` +
`(patch-id match). Restarted the branch from ${remoteMain} so new work does not stack ` +
`on merged history — this is what used to cause the every-session merge conflicts. ` +
`The old tip is recoverable via reflog. When you push, use --force-with-lease if the ` +
`remote branch still carries the old history.`,
);
}
} else if (unpushed > 0) {
notes.push(
`⚠️ "${branch}" carries ${unpushed} commit(s) not on any remote and not yet merged. ` +
`This container is ephemeral — push early (git push -u origin ${branch}).`,
);
}
}

emit(
[
`Cloud session on branch "${branch}" (default: ${defaultBranch}).`,
`Flow: commit here, push with git push -u origin ${branch}, open a PR via the GitHub MCP ` +
`tools (ready for review), subscribe_pr_activity on it, report the PR link, and END ` +
`THE TURN. Never wait or poll on CI — the subscription delivers failures as events, ` +
`silence is green (PRs run only the ~30s quick job). NEVER merge — no ` +
`merge_pull_request, no enable_pr_auto_merge. The user merges PRs themselves by asking ` +
`in chat; the PR sits parked until then, and a user-requested merge ends at "merged" — ` +
`no post-merge babysitting of ${defaultBranch}. A CI failure event → fix on the same ` +
`branch, push, end turn. ` +
`New task in the same session → fresh claude/... branch off origin/${defaultBranch}, ` +
`separate PR; never stack it on a parked branch. Never arm send_later check-ins or ` +
`scheduled remote sessions for CI. No worktrees — every session is its own isolated ` +
`cloud container.`,
...notes,
].join("\n\n") + shallowNote,
);
Loading
Loading