Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
2fac49e
localization: seat bounded literal evidence in primary
zzet Aug 12, 2026
303f466
explore: widen explicit quoted literal recall
zzet Aug 12, 2026
b9b0414
localization: extract bounded bare source literals
zzet Aug 12, 2026
5fadae4
localization: search unanchored bare literals
zzet Aug 12, 2026
01efee4
localization: cache node-only file declarations
zzet Aug 12, 2026
241b2fc
localization: complete the top two file outlines
zzet Aug 12, 2026
f760625
localization: promote packed body declarations
zzet Aug 12, 2026
d0d8c08
localization: bound case-folded exact-name fallback
zzet Aug 12, 2026
cc37f0a
localization: serve bounded literal source windows
zzet Aug 12, 2026
9c8c19e
localization: remove obsolete anchor wrapper
zzet Aug 12, 2026
3445343
localization: recover qualified leaf anchors
zzet Aug 12, 2026
f39772a
explore: add task-mode terminal parity
zzet Aug 12, 2026
98555e0
localization: authenticate bounded evidence IDs
zzet Aug 12, 2026
e829658
localization: persist terminal evidence authority
zzet Aug 13, 2026
1653fbb
test: verify terminal evidence marker roundtrip
zzet Aug 13, 2026
e4ddd61
hooks: challenge unsupported localization claims once
zzet Aug 13, 2026
fa287e8
codex: install and route localization claim checks
zzet Aug 13, 2026
bf9fd75
hooks: preserve UTF-8 in bounded claim checks
zzet Aug 13, 2026
d599af8
codex: bridge localization receipt lifecycle
zzet Aug 13, 2026
f660dbd
codex: recognize non-prefixed localization tools
zzet Aug 13, 2026
8e57bba
hooks: consume enforceable claim checks once
zzet Aug 13, 2026
e2e7caa
localization: reject unsafe evidence identities
zzet Aug 13, 2026
3f34bb7
localization: cap retained task declarations
zzet Aug 13, 2026
bdd1e93
explore: budget task outlines lazily
zzet Aug 13, 2026
570284a
hooks: normalize structured symbol claims
zzet Aug 13, 2026
02d5a68
localization: bound packed body promotion
zzet Aug 13, 2026
b2f0b9b
explore: require distinctive diagnostic literals
zzet Aug 13, 2026
ee29055
localization: scope case-folded anchor recovery
zzet Aug 13, 2026
946b0b2
test: cover session-scoped case-fold fallback
zzet Aug 13, 2026
da7f8c0
localization: prove qualified leaf ownership
zzet Aug 13, 2026
d2f005f
localization: shed low-rank outlines first
zzet Aug 13, 2026
89c9817
hooks: fix claim trim cutset lint
zzet Aug 13, 2026
bd2e9be
localization: preserve ranked task candidates
zzet Aug 13, 2026
3f1c3c8
localization: report bounded outline truncation
zzet Aug 13, 2026
7f42d66
localization: preserve task outline counts
zzet Aug 13, 2026
fdc0d08
localization: bound structured page outlines
zzet Aug 13, 2026
9afea63
localization: bound structured page outline files
zzet Aug 13, 2026
697d9d6
fix(localization): preserve literal provenance through projection
zzet Aug 13, 2026
c31928c
fix(localization): retain graph-resolved literal callees
zzet Aug 13, 2026
3654140
fix(localization): promote bounded direct adjacency
zzet Aug 13, 2026
99c314f
fix(localization): preserve adjacency evidence contracts
zzet Aug 13, 2026
a352282
fix(localization): preserve ranked primary cohort
zzet Aug 13, 2026
80bc315
fix(localization): propagate literal primary eligibility
zzet Aug 13, 2026
4e50597
fix(explore): keep task mode nonterminal
zzet Aug 13, 2026
e9ef9fa
fix(localization): honor request overlays
zzet Aug 13, 2026
f4003b6
fix(localization): shed supplemental digest rows first
zzet Aug 13, 2026
41d644a
fix(codex): enforce terminal gate for every tool
zzet Aug 13, 2026
a8fa35c
fix(localization): authenticate every final claim
zzet Aug 13, 2026
a980c5c
fix(codex): harden terminal hook boundary
zzet Aug 13, 2026
144af25
fix(localization): preserve claim identity across response
zzet Aug 13, 2026
9f7faf3
perf(localization): bound exact-name projections
zzet Aug 13, 2026
3e78ab9
fix(codex): collapse duplicate managed hooks
zzet Aug 13, 2026
caa043d
fix(localization): harden final claim parsing
zzet Aug 13, 2026
e3ef786
fix(localization): bound overlay shadow refill
zzet Aug 13, 2026
4dc58e2
perf(graph): add bounded file-node summaries
zzet Aug 13, 2026
d0b0716
fix(localization): close final claim parser bypasses
zzet Aug 13, 2026
f1ed2e2
perf(localization): bound file summary allocation
zzet Aug 13, 2026
8fd8a2f
fix(localization): distinguish claims from file syntax
zzet Aug 13, 2026
8fefc32
fix(graph): drop hidden localization sentinel
zzet Aug 13, 2026
bb373a3
fix(localization): preserve explicit claim syntax
zzet Aug 13, 2026
5067221
perf(mcp): bound localization file ownership
zzet Aug 13, 2026
eebc79a
feat(localization): scan bounded overlay literals
zzet Aug 13, 2026
7375523
fix(localization): charge inspected overlay bytes
zzet Aug 13, 2026
5aa26ab
fix(mcp): pin overlay state per request
zzet Aug 13, 2026
2938aee
feat(localization): merge bounded overlay literals
zzet Aug 13, 2026
f6404df
fix(localization): bind Markdown fences to containers
zzet Aug 13, 2026
35d57d3
perf(overlay): avoid missing-session write locks
zzet Aug 13, 2026
2faeb45
fix(mcp): canonicalize request overlay cohorts
zzet Aug 13, 2026
5a7f260
perf(localization): prioritize overlay literal evidence
zzet Aug 13, 2026
395bcf3
perf(mcp): share bounded file-summary budget
zzet Aug 14, 2026
8f71a78
perf(mcp): preserve localization match priority
zzet Aug 14, 2026
c251629
perf(mcp): bound AST enclosing projections
zzet Aug 14, 2026
f93a8dc
perf(localization): bound declaration projections
zzet Aug 14, 2026
6fd456a
perf(mcp): bound ranked-file exact-name recovery
zzet Aug 14, 2026
2853fa1
fix(graph): bound overlay localization shadows
zzet Aug 14, 2026
31f731b
fix(hooks): enforce bounded localization claims
zzet Aug 14, 2026
96ed1ae
perf(mcp): enrich only matched AST files
zzet Aug 14, 2026
714e554
perf(localization): align bounded outline depth
zzet Aug 14, 2026
53fdb46
perf(explore): bound causal owner recovery
zzet Aug 14, 2026
09da1cb
fix(overlay): bound request snapshots before materialization
zzet Aug 14, 2026
5fb688a
fix(mcp): bound overlay layer construction
zzet Aug 14, 2026
d048a98
fix(parser): bound subprocess extractor wire output
zzet Aug 14, 2026
0fb520f
fix(mcp): use bounded extractors for overlays
zzet Aug 14, 2026
d03b899
perf(explore): bound qualified-leaf edge proof
zzet Aug 14, 2026
1026f15
perf(graph): add bounded adjacency projections
zzet Aug 14, 2026
2816cb8
perf(mcp): bound source-literal adjacency
zzet Aug 14, 2026
f455efe
perf(mcp): bound typed-anchor graph projection
zzet Aug 14, 2026
ec1f29d
fix(mcp): bound implementation and owner recovery
zzet Aug 14, 2026
bc150ad
fix(mcp): bound search consumption attribution
zzet Aug 14, 2026
2a4993e
fix(explore): preserve owner fold insertion order
zzet Aug 14, 2026
45c1edd
fix(explore): retain explicit adjacency evidence
zzet Aug 14, 2026
756ddee
fix(explore): resolve scoped source citations
zzet Aug 14, 2026
30dc8cc
fix(explore): preserve exact source range owners
zzet Aug 14, 2026
0a4013c
fix(explore): reserve secondary task citation
zzet Aug 14, 2026
cb77bd4
chore(mcp): drop refactor-orphaned helpers
zzet Aug 15, 2026
643138c
feat(localize): seat task-named rows into the primary block across ad…
zzet Aug 15, 2026
3d0e09e
fix(localize): require whole-word task naming for seats and promote t…
zzet Aug 16, 2026
3a14da4
fix(localize): scope task naming to the immediate declaring owner
zzet Aug 16, 2026
c9dfe8e
fix(localize): extend the primary block for task-named rows instead o…
zzet Aug 16, 2026
128029b
feat(localize): ground quoted task literals in ranked primary evidence
zzet Aug 16, 2026
70da9d0
feat(localize): mine distinctive unquoted task tokens for source-lite…
zzet Aug 16, 2026
ebffc98
fix(localize): mine only structurally identifier-shaped bare tokens
zzet Aug 17, 2026
7133677
Merge remote-tracking branch 'origin/main' into feat/localization-clo…
zzet Aug 17, 2026
5b8ae73
fix(ci): satisfy tightened linters, refresh codex render golden, remo…
zzet Aug 17, 2026
a51b919
ci: raise the race-suite timeout to thirty minutes
zzet Aug 17, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
run: go build -o gortex ./cmd/gortex/

- name: Test
run: go test -race -timeout=20m -coverprofile=coverage.out ./...
run: go test -race -timeout=30m -coverprofile=coverage.out ./...

- name: Upload coverage
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.26'
Expand Down
44 changes: 20 additions & 24 deletions cmd/gortex/testdata/agent-render/codex.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ direct_only_tool_namespaces = ['mcp__gortex', 'gortex']

[hooks]
[[hooks.PostToolUse]]
matcher = '^(Bash|apply_patch)$'
matcher = '^(Bash|apply_patch|(mcp__gortex__|gortex__)(explore|search|read|relations|trace|analyze))$'

[[hooks.PostToolUse.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
Expand All @@ -14,20 +14,11 @@ timeout = 5
type = 'command'

[[hooks.PreToolUse]]
matcher = '^Bash$'
matcher = '.*'

[[hooks.PreToolUse.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
statusMessage = 'Loading Gortex Bash guidance...'
timeout = 5
type = 'command'

[[hooks.PreToolUse]]
matcher = '^mcp__gortex__read$'

[[hooks.PreToolUse.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
statusMessage = 'Loading Gortex read guidance...'
statusMessage = 'Loading Gortex tool guidance...'
timeout = 5
type = 'command'

Expand All @@ -40,6 +31,13 @@ statusMessage = 'Loading Gortex graph orientation...'
timeout = 5
type = 'command'

[[hooks.Stop]]
[[hooks.Stop.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
statusMessage = 'Checking Gortex evidence authority...'
timeout = 5
type = 'command'

[[hooks.UserPromptSubmit]]
[[hooks.UserPromptSubmit.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
Expand Down Expand Up @@ -607,7 +605,7 @@ direct_only_tool_namespaces = ['mcp__gortex', 'gortex']

[hooks]
[[hooks.PostToolUse]]
matcher = '^(Bash|apply_patch)$'
matcher = '^(Bash|apply_patch|(mcp__gortex__|gortex__)(explore|search|read|relations|trace|analyze))$'

[[hooks.PostToolUse.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
Expand All @@ -616,20 +614,11 @@ timeout = 5
type = 'command'

[[hooks.PreToolUse]]
matcher = '^Bash$'
matcher = '.*'

[[hooks.PreToolUse.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
statusMessage = 'Loading Gortex Bash guidance...'
timeout = 5
type = 'command'

[[hooks.PreToolUse]]
matcher = '^mcp__gortex__read$'

[[hooks.PreToolUse.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
statusMessage = 'Loading Gortex read guidance...'
statusMessage = 'Loading Gortex tool guidance...'
timeout = 5
type = 'command'

Expand All @@ -642,6 +631,13 @@ statusMessage = 'Loading Gortex graph orientation...'
timeout = 5
type = 'command'

[[hooks.Stop]]
[[hooks.Stop.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
statusMessage = 'Checking Gortex evidence authority...'
timeout = 5
type = 'command'

[[hooks.UserPromptSubmit]]
[[hooks.UserPromptSubmit.hooks]]
command = 'gortex hook --agent=codex --mode=enrich'
Expand Down
1 change: 1 addition & 0 deletions docs/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ Current Codex hook coverage:
| Surface | Coverage |
| ------- | -------- |
| `SessionStart` | Matches `startup|resume|clear|compact` and emits graph-tools orientation for new, resumed, cleared, and compacted sessions. |
| Hook-observable local-tool `PreToolUse` terminal gate | Uses one match-all installer-owned hook so an enforceable localization `answer_ready` contract blocks every local tool Codex routes through `PreToolUse`, including Bash, `apply_patch`, MCP tools, and other local function tools. Hosted tools such as `WebSearch`, and specialized paths that opt out of the default hook path, are outside this host boundary. Without a terminal marker it is a local no-op; advisory completion still permits non-navigation contract operations. |
| Bash `PreToolUse` | Advises by default; `deny` hard-blocks graph-detectable fallback reads/searches; `rewrite` converts only an unambiguous indexed `cat <source>` into the exact public `gortex call read` mirror. Compound or ambiguous commands remain advisory. A shell command that would *rewrite* indexed source — `sed -i` / `perl -pi`, a `>` / `>>` redirect, `tee`, or an inline interpreter script opening the path for writing — gets the same redirect Edit and Write get, escalating to a hard block under `GORTEX_HOOK_BLOCK_EDIT`. Writing a path the daemon does not know is a new file and passes through. |
| Gortex MCP `read` `PreToolUse` | Advises broad file/editing-context reads by default; `deny` blocks them; `rewrite` preserves the request and adds `options.compress_bodies=true`. Selector-driven reads with no explicit operation are covered. |
| Bash `PostToolUse` | Adds graph context for grep/search, source reads, and conservative file-list shapes: `find -name`, `fd`, `ls`, `tree -fi`, and `git ls-files`; bounded `sed`/`awk` reads get file graph context. Execution-capable or ambiguous forms are no-ops. |
Expand Down
136 changes: 111 additions & 25 deletions internal/agents/codex/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,20 @@ const (
v060CodexSessionStartMessage = "IMPORTANT: Prefer Gortex MCP tools (search_symbols, get_callers, get_file_summary, edit_file) over Read/Grep/Glob/Edit."
v060CodexSessionStartCommand = "printf '%s\\n' '" + v060CodexSessionStartMessage + "'"
v060CodexSessionStartWindowsCommand = "powershell -NoProfile -Command \"Write-Output '" + v060CodexSessionStartMessage + "'\""
codexPreToolUseMatcher = "^Bash$"
codexMCPReadPreToolUseMatcher = "^mcp__gortex__read$"
codexPostToolUseMatcher = "^(Bash|apply_patch)$"
codexHookTimeoutSeconds = 5
codexHookModeEnvVar = "GORTEX_CODEX_HOOK_MODE"
// Codex matchers are regular expressions. A match-all PreToolUse hook is
// required because terminal localization covers every local tool routed
// through Codex's hook path; hosted and specialized opt-out tools remain
// outside the host's hook boundary. Without a marker the handler is a
// strict local no-op.
codexPreToolUseMatcher = ".*"
// Retained as migration fingerprints in tests: upsertCodexHookSet removes
// both split predecessors by managed command identity before installing the
// singleton match-all hook.
codexLegacyBashPreToolUseMatcher = "^Bash$"
codexLegacyMCPNavigationPreToolUseMatcher = "^(mcp__gortex__|gortex__)(explore|search|read|relations|trace|analyze)$"
codexPostToolUseMatcher = "^(Bash|apply_patch|(mcp__gortex__|gortex__)(explore|search|read|relations|trace|analyze))$"
codexHookTimeoutSeconds = 5
codexHookModeEnvVar = "GORTEX_CODEX_HOOK_MODE"
// Codex merges its home instructions file into every session ahead of
// the repo's own AGENTS.md, preferring the override name when present.
codexGlobalInstructionsFile = "AGENTS.md"
Expand Down Expand Up @@ -550,11 +559,77 @@ func upsertSessionStartHook(root map[string]any, env agents.Env, opts agents.App
}

func upsertPreToolUseHook(root map[string]any, env agents.Env, opts agents.ApplyOpts) bool {
desired := []map[string]any{
codexPreToolUseHookEntry(env),
codexMCPReadPreToolUseHookEntry(env),
// Codex permits several handlers in one matcher group. Normalize at handler
// granularity before replacing legacy Gortex matchers: this preserves every
// co-located user handler while collapsing duplicate managed invocations.
splitChanged := splitMixedCodexPreToolUseGroups(root)
desired := []map[string]any{codexPreToolUseHookEntry(env)}
upsertChanged := upsertCodexHookSet(root, "PreToolUse", codexHookEntryIsGortexPreToolUse, desired, opts)
return splitChanged || upsertChanged
}

func splitMixedCodexPreToolUseGroups(root map[string]any) bool {
hooks, ok := root["hooks"].(map[string]any)
if !ok {
return false
}
entries, ok := codexHookList(hooks["PreToolUse"])
if !ok {
return false
}

normalized := make([]any, 0, len(entries))
changed := false
for _, entry := range entries {
group, ok := entry.(map[string]any)
if !ok {
normalized = append(normalized, entry)
continue
}
handlers, ok := codexHookList(group["hooks"])
if !ok {
normalized = append(normalized, entry)
continue
}
managed := 0
kept := make([]any, 0, len(handlers))
for _, handler := range handlers {
fields, ok := handler.(map[string]any)
if ok {
command, _ := fields["command"].(string)
if codexCommandInvokesCodexHook(command) {
managed++
continue
}
}
kept = append(kept, handler)
}
switch {
case managed == 0:
normalized = append(normalized, entry)
case managed == 1 && len(handlers) == 1:
// Leave a singleton managed group for upsertCodexHookSet to
// validate or replace. A current singleton remains idempotent.
normalized = append(normalized, entry)
default:
changed = true
if len(kept) == 0 {
continue
}
preserved := make(map[string]any, len(group))
for key, value := range group {
preserved[key] = value
}
preserved["hooks"] = kept
normalized = append(normalized, preserved)
}
}
if !changed {
return false
}
return upsertCodexHookSet(root, "PreToolUse", codexHookEntryIsGortexPreToolUse, desired, opts)
hooks["PreToolUse"] = normalized
root["hooks"] = hooks
return true
}

func upsertPostToolUseHook(root map[string]any, env agents.Env, opts agents.ApplyOpts) bool {
Expand All @@ -565,6 +640,10 @@ func upsertUserPromptSubmitHook(root map[string]any, env agents.Env, opts agents
return upsertCodexHookSet(root, "UserPromptSubmit", codexHookEntryIsGortexUserPromptSubmit, []map[string]any{codexUserPromptSubmitHookEntry(env)}, opts)
}

func upsertStopHook(root map[string]any, env agents.Env, opts agents.ApplyOpts) bool {
return upsertCodexHookSet(root, "Stop", codexHookEntryIsGortexStop, []map[string]any{codexStopHookEntry(env)}, opts)
}

// InstallHooksOnly refreshes the Codex lifecycle hooks in configPath without
// touching MCP server entries, AGENTS.md, or any other Codex adapter surface.
func InstallHooksOnly(w io.Writer, configPath string, env agents.Env, opts agents.ApplyOpts) (agents.FileAction, error) {
Expand All @@ -585,7 +664,8 @@ func upsertCodexHooks(root map[string]any, env agents.Env, opts agents.ApplyOpts
preChanged := upsertPreToolUseHook(root, env, opts)
postChanged := upsertPostToolUseHook(root, env, opts)
promptChanged := upsertUserPromptSubmitHook(root, env, opts)
return sessionChanged || preChanged || postChanged || promptChanged
stopChanged := upsertStopHook(root, env, opts)
return sessionChanged || preChanged || postChanged || promptChanged || stopChanged
}

func upsertCodexHookSet(root map[string]any, event string, isGortex func(any) bool, desired []map[string]any, opts agents.ApplyOpts) bool {
Expand Down Expand Up @@ -734,6 +814,10 @@ func codexHookEntryIsGortexUserPromptSubmit(entry any) bool {
return codexHookEntryInvokesCodexHook(entry)
}

func codexHookEntryIsGortexStop(entry any) bool {
return codexHookEntryInvokesCodexHook(entry)
}

func codexHookEntryInvokesCodexHook(entry any) bool {
group, ok := entry.(map[string]any)
if !ok {
Expand Down Expand Up @@ -789,21 +873,7 @@ func codexPreToolUseHookEntry(env agents.Env) map[string]any {
"type": "command",
"command": codexPreToolUseCommand(env),
"timeout": codexHookTimeoutSeconds,
"statusMessage": "Loading Gortex Bash guidance...",
},
},
}
}

func codexMCPReadPreToolUseHookEntry(env agents.Env) map[string]any {
return map[string]any{
"matcher": codexMCPReadPreToolUseMatcher,
"hooks": []any{
map[string]any{
"type": "command",
"command": codexPreToolUseCommand(env),
"timeout": codexHookTimeoutSeconds,
"statusMessage": "Loading Gortex read guidance...",
"statusMessage": "Loading Gortex tool guidance...",
},
},
}
Expand Down Expand Up @@ -841,6 +911,22 @@ func codexUserPromptSubmitHookEntry(env agents.Env) map[string]any {
}
}

// codexStopHookEntry has no matcher: Stop applies to every final response.
// Older Codex hosts that omit last_assistant_message remain fail-open in the
// shared Stop handler.
func codexStopHookEntry(env agents.Env) map[string]any {
return map[string]any{
"hooks": []any{
map[string]any{
"type": "command",
"command": codexHookCommand(env),
"timeout": codexHookTimeoutSeconds,
"statusMessage": "Checking Gortex evidence authority...",
},
},
}
}

func codexPreToolUseCommand(env agents.Env) string {
return codexHookCommand(env)
}
Expand Down
Loading
Loading