Skip to content

Add semantic policy injection#261

Merged
paudley merged 4 commits into
mainfrom
paudley/56-agent-proxy-jit-semantic-policy-injection
Jun 24, 2026
Merged

Add semantic policy injection#261
paudley merged 4 commits into
mainfrom
paudley/56-agent-proxy-jit-semantic-policy-injection

Conversation

@paudley

@paudley paudley commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Closes #56

Summary

  • add deterministic PreToolUse semantic policy injection for mutating Git intents and Python file targets
  • keep read-only Git inspection quiet while pointing mutating Git calls to safe-git-workflow and policy-git
  • compose advisory hook contexts so session-start guidance remains visible alongside other hook advice
  • document the just-in-time injection behavior in README.md and docs/AGENT_PROXY.md

Validation

  • go test -buildvcs=false ./go/internal/hooks -run 'TestRun(InjectsSemanticPolicyForGitMutation|SkipsSemanticPolicyForReadOnlyGitInspection|InjectsSemanticPolicyForPythonFileTarget)|TestEncodeGeminiSemanticPolicyInjection' -count=1 -v
  • go test -buildvcs=false ./go/internal/hooks -run TestRunInjectsContextAdviceOnSessionStartWhenThresholdCrossed -count=1 -v
  • bin/coding-ethos-run policy-tool golangci-lint ./go/internal/hooks/...
  • make build
  • make check (passes; existing non-blocking testing.go_coverage_goal warning remains)
  • bin/coding-ethos-run policy-git diff --check

Copilot AI review requested due to automatic review settings June 24, 2026 02:46
@paudley paudley requested a review from ErinAudley as a code owner June 24, 2026 02:46
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 792501ce-0843-489d-a066-74cb37509fc2

📥 Commits

Reviewing files that changed from the base of the PR and between c6db516 and a4279fe.

📒 Files selected for processing (9)
  • README.md
  • docs/AGENT_PROXY.md
  • go/internal/hookrunnercli/toolchain_groups.go
  • go/internal/hooks/runner.go
  • go/internal/hooks/runner_test.go
  • go/internal/hooks/semantic_policy_injection.go
  • go/internal/hooks/semantic_policy_injection_test.go
  • go/internal/mcp/server_test.go
  • go/internal/webguidancecli/main_internal_test.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added turn-scoped semantic policy guidance for specific tool calls, including mutating Git actions and Python file reads.
    • Expanded hook output so multiple advisory messages can appear together in a single turn.
  • Bug Fixes

    • Improved detection of Git intent, including commands with global options and wrapper-based invocations.
    • Read-only Git inspection commands now stay quiet as expected.
  • Documentation

    • Updated user-facing docs to explain when policy guidance appears and when it is suppressed.

Walkthrough

Adds PreToolUse semantic policy injection for Git mutation and Python file targets, merges the new hook output into runner aggregation, updates push handling constants, and replaces several fixed cache timestamps with current UTC time in tests.

Changes

Semantic Policy Injection via PreToolUse Hook

Layer / File(s) Summary
Push constant and wrapper checks
go/internal/hooks/runner.go, go/internal/hooks/git_wrapper_enforcement.go
Introduces operationPush and replaces push string comparisons in hook operation detection and wrapper-runner matching.
Semantic policy detection and rendering
go/internal/hooks/semantic_policy_injection.go, go/internal/hooks/semantic_policy_injection_test.go
Adds PreToolUse semantic policy injection for Git mutation and Python file targets, including Git command parsing, skill selection, context rendering, and shell-command mutation tests.
Runner merge wiring and hook tests
go/internal/hooks/runner.go, go/internal/hooks/runner_test.go
Merges semantic policy output into advisory hook output selection and extends runner tests for Git mutation, read-only Git inspection, Python file targets, and Gemini encoding.
Documentation updates
README.md, docs/AGENT_PROXY.md
Documents the new PreToolUse semantic policy injection behavior and capability notes.

Cache Time Sources

Layer / File(s) Summary
Current-time cache seeding
go/internal/mcp/server_test.go, go/internal/webguidancecli/main_internal_test.go
Replaces fixed UTC timestamps with time.Now().UTC() in cache-seeding and cache-read tests.

Sequence Diagram(s)

sequenceDiagram
    participant Agent
    participant HookRunner
    participant SemanticPolicyInjection
    participant ProviderEncoder

    Agent->>HookRunner: PreToolUse tool call
    HookRunner->>SemanticPolicyInjection: event, bundle
    SemanticPolicyInjection-->>HookRunner: HookSpecificOutput or nil
    HookRunner->>HookRunner: merge advisory outputs
    HookRunner->>ProviderEncoder: EncodeProviderResult(merged output)
    ProviderEncoder-->>Agent: injected AdditionalContext
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • ErinAudley
  • Copilot

Poem

🐰 I hopped through hooks both swift and neat,
For Git and Python, context found its seat.
Quiet on status, loud on a commit run,
Then time itself stopped using fixed-time fun.
Lean rules arrived just when they were due —
A tidy little hop for me and you.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add semantic policy injection' clearly and directly describes the main feature being added.
Description check ✅ Passed The PR description comprehensively covers what changed, why, validation steps, and links to issue #56. All major template sections are addressed.
Linked Issues check ✅ Passed The PR fully implements semantic policy injection for Git mutations and Python file targets as specified in issue #56, with deterministic rule-based triggering.
Out of Scope Changes check ✅ Passed All changes are directly aligned with implementing semantic policy injection as required in issue #56; no out-of-scope modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch paudley/56-agent-proxy-jit-semantic-policy-injection

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linter] gci: create analyzer: finding local modules for localModule configuration: reading go.mod: open go.mod: no such file or directory"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces just-in-time semantic policy injection during the PreToolUse hook. It detects mutating Git commands and Python file targets in incoming tool calls to inject relevant skill pointers and policy guidance instead of front-loading them at startup. It also includes documentation updates, helper functions to merge advisory hook outputs, and comprehensive tests. Feedback on the changes suggests improving Git mutation detection in shellCommandMutatesGit to handle global flags (e.g., git -C <path> commit) by scanning the arguments slice using slices.ContainsFunc, and simplifying the allocation of merged in mergeAdvisoryHookOutputs to avoid taking the address of a block-local variable.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +103 to +121
func shellCommandMutatesGit(command shellparse.Command) bool {
args := command.Argv
if len(args) == 0 {
return false
}

switch shellCommandName(command) {
case tokenGit:
return len(args) > 1 && semanticGitMutation(args[1])
case wrapperRunnerName:
return len(args) > 2 &&
args[1] == "policy-git" &&
semanticGitMutation(args[2])
case "coding-ethos-git":
return len(args) > 1 && semanticGitMutation(args[1])
}

return false
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of shellCommandMutatesGit only checks args[1] (or args[2] for policy-git) to detect mutating Git subcommands. If global flags (such as git -C <path> commit or git -c <config> commit) are used, the mutating subcommand is shifted to a later index, causing the detection to fail and skipping the semantic policy injection.

Using slices.ContainsFunc to scan the arguments slice is much more robust and handles global flags gracefully.

Suggested change
func shellCommandMutatesGit(command shellparse.Command) bool {
args := command.Argv
if len(args) == 0 {
return false
}
switch shellCommandName(command) {
case tokenGit:
return len(args) > 1 && semanticGitMutation(args[1])
case wrapperRunnerName:
return len(args) > 2 &&
args[1] == "policy-git" &&
semanticGitMutation(args[2])
case "coding-ethos-git":
return len(args) > 1 && semanticGitMutation(args[1])
}
return false
}
func shellCommandMutatesGit(command shellparse.Command) bool {
args := command.Argv
if len(args) == 0 {
return false
}
switch shellCommandName(command) {
case tokenGit:
return slices.ContainsFunc(args[1:], semanticGitMutation)
case wrapperRunnerName:
return len(args) > 2 &&
args[1] == "policy-git" &&
slices.ContainsFunc(args[2:], semanticGitMutation)
case "coding-ethos-git":
return slices.ContainsFunc(args[1:], semanticGitMutation)
}
return false
}

Comment on lines +332 to +336
if merged == nil {
next := *output
next.AdditionalContext = ""
merged = &next
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Taking the address of a block-local variable next scoped inside the if block is a Go code smell and can lead to unnecessary heap allocations. Since we already ensure that PermissionDecision, PermissionDecisionReason, and UpdatedInput are empty before this block, we can simplify this by directly allocating a new HookSpecificOutput with the HookEventName copied over.

Suggested change
if merged == nil {
next := *output
next.AdditionalContext = ""
merged = &next
}
if merged == nil {
merged = &HookSpecificOutput{
HookEventName: output.HookEventName,
}
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@go/internal/hooks/runner_test.go`:
- Around line 4587-4622: The test TestEncodeGeminiSemanticPolicyInjection only
validates that expected substrings exist in the encoded output using
strings.Contains, but does not verify that the encoded JSON is well-formed.
After calling EncodeProviderResult and obtaining the encoded string, parse it as
JSON into a struct or map to validate the JSON structure is valid, and then
verify the expected fields and values exist in the parsed JSON object rather
than just checking for substring presence. This ensures both that the JSON is
properly formatted and that the expected data is in the correct structure.

In `@go/internal/hooks/semantic_policy_injection.go`:
- Around line 103-155: The shellCommandMutatesGit function only inspects fixed
argument positions (args[1] for tokenGit and "coding-ethos-git", args[2] for
wrapperRunnerName) without accounting for leading global options that start with
a dash. This causes commands like "git -C /repo commit" to be missed because
"-C" is at args[1] instead of the actual "commit" subcommand. Update
shellCommandMutatesGit to scan past leading options (tokens starting with "-")
in the argument list for each case statement (tokenGit, wrapperRunnerName, and
"coding-ethos-git") to find the first non-option token, then pass that token to
semanticGitMutation instead of checking fixed indices.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fdd7b685-bfda-4f94-b3c9-b2069cffeef2

📥 Commits

Reviewing files that changed from the base of the PR and between 8dc4fab and c6db516.

📒 Files selected for processing (6)
  • README.md
  • docs/AGENT_PROXY.md
  • go/internal/hooks/git_wrapper_enforcement.go
  • go/internal/hooks/runner.go
  • go/internal/hooks/runner_test.go
  • go/internal/hooks/semantic_policy_injection.go
📜 Review details
⏰ Context from checks skipped due to timeout. (8)
  • GitHub Check: Coding Ethos SARIF Gate / Coding Ethos SARIF Gate
  • GitHub Check: Test (Python 3.11)
  • GitHub Check: Test (Python 3.13)
  • GitHub Check: Go coverage
  • GitHub Check: Unified lint
  • GitHub Check: Validate GitHub workflows
  • GitHub Check: CodeQL (go)
  • GitHub Check: Go fuzz smoke
🧰 Additional context used
📓 Path-based instructions (1)
**/{hooks,policies,lint-capture,runtime}/**/*.{go,sh,py}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer compiled Go for hook, policy, lint-capture, and runtime glue; treat shell and Python as transitional unless they are clearly the right interface

Files:

  • go/internal/hooks/runner_test.go
  • go/internal/hooks/semantic_policy_injection.go
  • go/internal/hooks/git_wrapper_enforcement.go
  • go/internal/hooks/runner.go
🧠 Learnings (1)
📚 Learning: 2026-06-23T12:26:47.373Z
Learnt from: paudley
Repo: paudley/coding-ethos PR: 258
File: go/internal/geminiprompts/render.go:125-132
Timestamp: 2026-06-23T12:26:47.373Z
Learning: In the paudley/coding-ethos repo, the documented guideline “Gemini prompt pack should be generated in go/internal/geminiprompts/” refers to the generator implementation location (Go source), not the location of the generated prompt-pack artifact. Always treat PromptPackPath as the enforced output artifact path `.coding-ethos/gemini/prompt-pack.json` (e.g., render_test.go asserts PromptPackPath, and compiler_policies.go / hookrunner lookup / README document the expected path). Do not flag a code review issue just because PromptPackPath points to `.coding-ethos/gemini/prompt-pack.json`; only flag violations if the artifact output path logic breaks that enforced contract.

Applied to files:

  • go/internal/hooks/runner_test.go
  • go/internal/hooks/semantic_policy_injection.go
  • go/internal/hooks/git_wrapper_enforcement.go
  • go/internal/hooks/runner.go
🔇 Additional comments (9)
go/internal/hooks/runner_test.go (2)

7-7: LGTM!


4503-4585: LGTM!

README.md (1)

673-679: LGTM!

docs/AGENT_PROXY.md (1)

362-380: LGTM!

go/internal/hooks/runner.go (3)

36-36: LGTM!

Also applies to: 525-537, 547-556


293-301: LGTM!


316-350: 🎯 Functional Correctness

Advisory outputs never populate decision fields, so the early return never occurs.

All five advisory output functions passed to mergeAdvisoryHookOutputs (semanticPolicyInjectionOutput, sessionMemoryImportOutput, continuationOutput, lifecycleOutput, postEditOutput) populate only AdditionalContext and never set PermissionDecision, PermissionDecisionReason, or UpdatedInput. The early return condition in mergeAdvisoryHookOutputs cannot be triggered by these functions, so accumulated context is not discarded in practice.

			> Likely an incorrect or invalid review comment.
go/internal/hooks/git_wrapper_enforcement.go (1)

151-165: LGTM!

Also applies to: 590-614

go/internal/hooks/semantic_policy_injection.go (1)

26-101: 🩺 Stability & Availability

All referenced symbols are defined and resolve correctly in the package. wrapperRunnerPath and wrapperRunnerName are defined in git_wrapper_enforcement.go, tokenGit is defined in git_wrapper_enforcement.go, toonCell is a function in output_format.go, and Event.Command() and Event.Files() are methods on the Event type in event.go. The code compiles without cross-file resolution issues.

			> Likely an incorrect or invalid review comment.

Comment thread go/internal/hooks/runner_test.go
Comment thread go/internal/hooks/semantic_policy_injection.go
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 24, 2026
@paudley

paudley commented Jun 24, 2026

Copy link
Copy Markdown
Owner Author

Stage 2 remediation is pushed.

Commit: 4adf67b fix(proxy): handle semantic policy review feedback

Feedback addressed:

  • Gemini/CodeRabbit global Git option detection: shellCommandMutatesGit now finds the Git subcommand after leading global options such as -C, -c, --git-dir, and --work-tree, with direct detector coverage for git, policy-git, and coding-ethos-git forms.
  • Gemini allocation feedback: mergeAdvisoryHookOutputs now allocates the merged output directly instead of taking the address of a block-local copy.
  • CodeRabbit JSON test feedback: TestEncodeGeminiSemanticPolicyInjection now decodes provider output as JSON and asserts structured fields before checking injected context.

Validation:

  • go test -buildvcs=false ./go/internal/hooks -run "TestShellCommandMutatesGitSkipsGlobalOptions|TestRun(InjectsSemanticPolicyForGitMutation|InjectsSemanticPolicyForGitMutationWithGlobalOptions|SkipsSemanticPolicyForReadOnlyGitInspection|SkipsSemanticPolicyForReadOnlyGitInspectionWithGlobalOptions|InjectsSemanticPolicyForPythonFileTarget|InjectsContextAdviceOnSessionStartWhenThresholdCrossed)|TestEncodeGeminiSemanticPolicyInjection" -count=1 -v
  • bin/coding-ethos-run policy-tool golangci-lint ./go/internal/hooks/...
  • make build
  • make check (passed; existing non-blocking testing.go_coverage_goal warning remains)
  • bin/coding-ethos-run policy-git diff --check

Current status:

  • The old review threads remain unresolved in GitHub until reviewer state refreshes.
  • CodeRabbit reported a review rate limit on the new commit, so its prior CHANGES_REQUESTED review may remain sticky even though the requested changes are addressed.

@paudley paudley enabled auto-merge (squash) June 24, 2026 03:13
@paudley

paudley commented Jun 24, 2026

Copy link
Copy Markdown
Owner Author

Stage 2 final status.

Pushed commits:

  • c6db516 feat(proxy): add semantic policy injection
  • 4adf67b fix(proxy): handle semantic policy review feedback

Validation and CI:

  • Local make build passed.
  • Local make check passed with the existing non-blocking testing.go_coverage_goal warning.
  • Managed golangci for ./go/internal/hooks/... passed.
  • GitHub checks are passing, including Unified lint, Go coverage, SARIF gate, Python 3.11/3.13, CodeQL, OSV, Scorecard, Zizmor, and CLA.
  • CodeRabbit check is passing and reports review approved after the remediation commit.

Merge status:

  • Direct squash merge was refused by base branch policy.
  • Squash auto-merge is enabled for this PR.
  • Remaining blocker is required review: GitHub reports reviewDecision: REVIEW_REQUIRED and mergeStateStatus: BLOCKED.

No admin override was used.

Copilot AI review requested due to automatic review settings June 24, 2026 15:03
@paudley paudley merged commit c6f4f15 into main Jun 24, 2026
16 of 18 checks passed
@paudley paudley deleted the paudley/56-agent-proxy-jit-semantic-policy-injection branch June 24, 2026 15:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Agent Proxy: Just-In-Time Semantic Policy Injection

2 participants