Skip to content

t1303: Soft TTSR rule engine — rule loader and rule files#2136

Merged
marcusquinn merged 3 commits intomainfrom
feature/t1303
Feb 22, 2026
Merged

t1303: Soft TTSR rule engine — rule loader and rule files#2136
marcusquinn merged 3 commits intomainfrom
feature/t1303

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Feb 22, 2026

Summary

  • Add .agents/rules/ directory with TTSR (Think-Then-Self-Reflect) soft rule engine
  • Implement ttsr-rule-loader.sh — discovers, parses, and checks rule files against AI output
  • Include 5 example rules: no-edit-on-main, no-hardcoded-secrets, no-glob-for-discovery, no-cat-for-reading, no-todo-edit-by-worker

Design

Rules are markdown files with YAML frontmatter (matching existing agent file conventions):

  • ttsr_trigger: ERE regex matched against AI output text
  • repeat_policy: once | after-gap | always — controls firing frequency
  • severity: info | warn | error — categorizes correction urgency
  • State tracking via simple file-based state (rule_id:turn pairs)

Commands: list, check, show, reset. Supports text and JSON output formats.

This is Phase 1 (soft TTSR — rules checked after output). Phase 2 will add real-time stream hook integration when supported.

Verification

  • ShellCheck: zero violations
  • All 5 rules parse and match correctly
  • Repeat policies verified (once, after-gap with gap cooldown, always)
  • Bash 3.2 compatible (macOS default)
  • JSON and text output formats tested
  • Stdin input and multiple simultaneous matches tested

Ref #2127

Summary by CodeRabbit

Release Notes

  • New Features

    • Implemented AI agent guardrails for security and efficiency: protecting against hardcoded secrets, main branch modifications, and unauthorized task file edits
    • Added rules optimizing file discovery and content reading methods
    • Introduced rule management and validation system
  • Documentation

    • Added comprehensive guide for the new guardrails system

Chose markdown-with-YAML-frontmatter format for rules — matches existing agent
file conventions in .agents/. Rule loader is a standalone shell script that
discovers, parses, and checks rules without external dependencies.
… handling (t1303)

Parser reads values literally — no YAML escape interpretation. Rule authors
write raw ERE regex without quotes or double-escaping. Fixed ${var^^} bashism
(requires bash 4+) and stdin "-" argument parsing.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

Walkthrough

This PR establishes a comprehensive TTSR (Think-Then-Self-Reflect) rule system for AI agent output validation. It introduces a framework documentation, five guardrail rules covering security and operational best practices, and a Bash script to load, parse, and enforce rules against agent output with state tracking.

Changes

Cohort / File(s) Summary
Documentation
.agents/rules/README.md
Describes YAML frontmatter format (id, ttsr_trigger, severity, repeat_policy, gap_turns, tags, enabled) and rule body structure; documents repeat policies and Phase 2 extension mechanism.
TTSR Rule Files
.agents/rules/no-cat-for-reading.md, no-edit-on-main.md, no-glob-for-discovery.md, no-hardcoded-secrets.md, no-todo-edit-by-worker.md
Five metadata-driven guardrail rules: prefer Read tool over cat/head/tail, prevent edits on main/master branch, use git/fd/rg for discovery, block hardcoded secrets, and restrict worker edits to planning files.
Rule Loader Script
.agents/scripts/ttsr-rule-loader.sh
Bash script implementing rule discovery, YAML frontmatter parsing, regex-based trigger evaluation, repeat policy handling (once/after-gap/always), state persistence, and multi-format output (text/JSON) via list/check/reset/show commands.

Sequence Diagram

sequenceDiagram
    participant Agent as Agent Output
    participant Loader as Rule Loader
    participant Rules as Rules Directory
    participant State as State File
    participant Output as Correction Output

    Agent->>Loader: check(output_text, turn_number)
    Loader->>Rules: read rules/*.md
    Loader->>Loader: parse YAML frontmatter & rule bodies
    Loader->>State: load last-fired turn per rule
    Loader->>Loader: for each enabled rule: evaluate ttsr_trigger regex
    alt Rule matches & repeat policy satisfied
        Loader->>State: record current turn as last-fired
        State-->>Loader: state updated
        Loader->>Output: emit correction (id, severity, body)
    else No match or policy blocks
        Loader->>Output: skip rule
    end
    Output-->>Agent: matched corrections in text/JSON
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • #343: Adds complementary main-branch write restrictions alongside the no-edit-on-main TTSR rule.
  • #1160: Overlaps with enforcement of PR+CI policies that the new no-edit-on-main rule reinforces.
  • #114: Related to planning file restrictions via no-todo-edit-by-worker rule and pre-edit-check integration.

Suggested Labels

needs-review

Poem

🤖 Rules now thinking, then they self-reflect,
Guardrails in YAML—patterns are correct,
The loader script parses with grace and care,
State-tracked wisdom floating in the air! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically describes the main change: introduction of a Soft TTSR rule engine with a rule loader script and rule files.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/t1303

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 and usage tips.

@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 38 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Sun Feb 22 04:37:19 UTC 2026: Code review monitoring started
Sun Feb 22 04:37:19 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 38

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 38
  • VULNERABILITIES: 0

Generated on: Sun Feb 22 04:37:21 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@marcusquinn marcusquinn marked this pull request as ready for review February 22, 2026 04:37
@gemini-code-assist
Copy link

Summary of Changes

Hello @marcusquinn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a foundational soft 'Think-Then-Self-Reflect' (TTSR) rule engine. Its primary purpose is to enhance AI agent reliability and adherence to best practices by automatically identifying undesirable patterns in their output and injecting corrective guidance. This system provides a structured way to enforce operational and security policies without direct code changes to the AI agent itself.

Highlights

  • TTSR Rule Engine Introduced: A new soft 'Think-Then-Self-Reflect' (TTSR) rule engine has been added under the .agents/rules/ directory to detect patterns in AI agent output and provide corrective instructions.
  • Rule Loader Script: The ttsr-rule-loader.sh script was implemented to discover, parse, and check rule files against AI output, acting as the core of the new rule engine.
  • Initial Rule Set: Five example rules were included to address common issues such as preventing direct edits on main branches, avoiding hardcoded secrets, promoting efficient file discovery, discouraging cat for file reading, and restricting worker edits to TODO files.
  • Configurable Rule Format: Rules are defined in Markdown files with YAML frontmatter, allowing for flexible configuration of id, ttsr_trigger (regex), severity, repeat_policy, gap_turns, tags, and enabled status.
  • Stateful Rule Execution: The engine incorporates state management to track when rules last fired, enabling once, after-gap, and always repeat policies for nuanced rule application.
Changelog
  • .agents/rules/README.md
    • Documented the purpose and structure of the TTSR rule directory.
    • Detailed the YAML frontmatter format for defining rules, including required and optional fields.
    • Explained the behavior of different repeat policies and provided instructions for adding new rules.
  • .agents/rules/no-cat-for-reading.md
    • Added a rule to discourage the use of cat, head, or tail for reading files, advocating for the Read tool instead.
  • .agents/rules/no-edit-on-main.md
    • Added a critical rule to prevent AI agents from making direct modifications on main or master branches, enforcing git workflow best practices.
  • .agents/rules/no-glob-for-discovery.md
    • Added a rule to promote more efficient file discovery methods over generic Glob patterns for better performance.
  • .agents/rules/no-hardcoded-secrets.md
    • Added a security rule to prevent the hardcoding of sensitive information such as API keys or passwords in AI output.
  • .agents/rules/no-todo-edit-by-worker.md
    • Added a workflow rule to restrict AI workers from editing specific TODO or planning files, maintaining supervisor control over planning documents.
  • .agents/scripts/ttsr-rule-loader.sh
    • Implemented the core Bash script responsible for discovering and loading TTSR rule files.
    • Developed a robust YAML frontmatter parser to extract rule metadata from Markdown files.
    • Integrated a regex-based trigger mechanism to match AI output against defined rule patterns.
    • Created state management functions to track rule firing and enforce once, after-gap, and always repeat policies.
    • Provided a command-line interface supporting list, check, reset, and show operations for rule management.
    • Added support for both human-readable text and machine-parseable JSON output formats.
Activity
  • ShellCheck verification was performed, resulting in zero violations.
  • All five included example rules were confirmed to parse and match correctly.
  • The once, after-gap, and always repeat policies were thoroughly tested and verified for correct behavior.
  • Compatibility with Bash 3.2 (the macOS default) was ensured.
  • Both JSON and text output formats were validated for proper rendering.
  • Stdin input and scenarios involving multiple simultaneous rule matches were successfully tested.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 38 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Sun Feb 22 04:38:14 UTC 2026: Code review monitoring started
Sun Feb 22 04:38:15 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 38

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 38
  • VULNERABILITIES: 0

Generated on: Sun Feb 22 04:38:17 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@sonarqubecloud
Copy link

Copy link

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

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 a soft rule engine (TTSR) for providing feedback on AI agent output, which is a great addition for improving agent behavior. The implementation includes a rule loader script and several example rules. The script is well-structured, but I've identified several critical and medium severity issues. The most significant concerns are around unsafe manual JSON generation which can lead to invalid output, suppression of important error messages that can mask configuration issues, and a design flaw in the state management that prevents state from persisting correctly across script invocations. These points are reinforced by established repository rules. I've provided detailed comments and suggestions to address these points, primarily recommending the use of jq for safe JSON handling and removing error suppression to improve robustness.


# Default rules directory: relative to repo root (one level up from scripts/)
DEFAULT_RULES_DIR="${SCRIPT_DIR}/../rules"
DEFAULT_STATE_FILE="/tmp/ttsr-state-$$"

Choose a reason for hiding this comment

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

high

The default state file is named /tmp/ttsr-state-$$. This creates a new, unique state file for every invocation of the script, which prevents state (like for the once and after-gap repeat policies) from being preserved across multiple check calls within the same session. The state file name should be stable within a session, for example, based on the parent process ID ($PPID) or a session ID passed via an environment variable.

Additionally, if a file in /tmp is intended to be temporary, the style guide requires it to be cleaned up via a trap. The current implementation lacks this. Given the stateful nature of the rules, a more persistent and session-stable file path would be more appropriate than a PID-based temporary file.

References
  1. Temporary files must be cleaned up using a trap on script exit. The default state file in /tmp is not cleaned up. (link)
  2. For resource cleanup in shell scripts, use the established project pattern: use _save_cleanup_scope, trap '_run_cleanups' RETURN, and push_cleanup for robust cleanup on any exit path, and also include explicit manual cleanup at the end of the normal execution path as a 'fast-path'.

Comment on lines +369 to +371
printf ' {"id":"%s","trigger":"%s","severity":"%s","repeat_policy":"%s","gap_turns":%s,"enabled":%s,"tags":"%s","file":"%s"}' \
"$rule_id" "$rule_trigger" "$rule_severity" "$rule_repeat_policy" \
"$rule_gap_turns" "$rule_enabled" "$rule_tags" "$rule_file"

Choose a reason for hiding this comment

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

high

Manually constructing JSON with printf is unsafe. If any of the variables contain special characters like quotes or backslashes, it will produce invalid JSON. It's much safer to use jq with --arg and --argjson to ensure all values are correctly escaped.

Suggested change
printf ' {"id":"%s","trigger":"%s","severity":"%s","repeat_policy":"%s","gap_turns":%s,"enabled":%s,"tags":"%s","file":"%s"}' \
"$rule_id" "$rule_trigger" "$rule_severity" "$rule_repeat_policy" \
"$rule_gap_turns" "$rule_enabled" "$rule_tags" "$rule_file"
printf ' %s' "$(jq -c -n \
--arg id "$rule_id" \
--arg trigger "$rule_trigger" \
--arg severity "$rule_severity" \
--arg repeat_policy "$rule_repeat_policy" \
--argjson gap_turns "$rule_gap_turns" \
--argjson enabled "$rule_enabled" \
--arg tags "$rule_tags" \
--arg file "$rule_file" \
'{id: $id, trigger: $trigger, severity: $severity, repeat_policy: $repeat_policy, gap_turns: $gap_turns, enabled: $enabled, tags: $tags, file: $file}')"
References
  1. In shell scripts, use jq --arg for strings and --argjson for other JSON types (like numbers) to safely pass variables into a jq filter. This avoids syntax errors if the variables contain special characters.

fi

# Check if trigger matches the output text
if printf '%s' "$output_text" | grep -qE "$rule_trigger" 2>/dev/null; then

Choose a reason for hiding this comment

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

high

Suppressing grep's standard error with 2>/dev/null can hide important errors, such as an invalid regular expression in a rule file. This would cause the rule to silently fail to match. Since the if statement correctly handles the "no match" case (exit code 1), the error suppression is not needed and should be removed to make debugging easier.

Suggested change
if printf '%s' "$output_text" | grep -qE "$rule_trigger" 2>/dev/null; then
if printf '%s' "$output_text" | grep -qE "$rule_trigger"; then
References
  1. Avoid using '2>/dev/null' for blanket suppression of command errors in shell scripts to ensure that authentication, syntax, or system issues remain visible for debugging.
  2. In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.

Comment on lines +428 to +450
if [[ "$format" == "json" ]]; then
if [[ "$matched" -eq 1 ]]; then
corrections='['
else
corrections="${corrections},"
fi
# Escape body for JSON (backslashes, quotes, newlines)
local escaped_body
escaped_body="$(printf '%s' "$rule_body" | awk '
BEGIN { ORS="" }
{
gsub(/\\/, "\\\\")
gsub(/"/, "\\\"")
if (NR > 1) printf "\\n"
printf "%s", $0
}
')"
corrections="${corrections}{\"id\":\"${rule_id}\",\"severity\":\"${rule_severity}\",\"body\":\"${escaped_body}\"}"
else
local severity_upper
severity_upper="$(printf '%s' "$rule_severity" | tr '[:lower:]' '[:upper:]')"
corrections="${corrections}--- [${severity_upper}] Rule: ${rule_id} ---"$'\n'"${rule_body}"$'\n'
fi

Choose a reason for hiding this comment

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

high

The current implementation for creating JSON output is unsafe. It manually builds JSON strings and uses a custom awk script for escaping, which is incomplete (it doesn't handle all special characters like tabs or control characters) and can produce invalid JSON. This also applies to the id and severity fields, which are not escaped at all. A much more robust and secure approach is to use jq to construct the JSON objects, which correctly handles all necessary escaping.

Suggested change
if [[ "$format" == "json" ]]; then
if [[ "$matched" -eq 1 ]]; then
corrections='['
else
corrections="${corrections},"
fi
# Escape body for JSON (backslashes, quotes, newlines)
local escaped_body
escaped_body="$(printf '%s' "$rule_body" | awk '
BEGIN { ORS="" }
{
gsub(/\\/, "\\\\")
gsub(/"/, "\\\"")
if (NR > 1) printf "\\n"
printf "%s", $0
}
')"
corrections="${corrections}{\"id\":\"${rule_id}\",\"severity\":\"${rule_severity}\",\"body\":\"${escaped_body}\"}"
else
local severity_upper
severity_upper="$(printf '%s' "$rule_severity" | tr '[:lower:]' '[:upper:]')"
corrections="${corrections}--- [${severity_upper}] Rule: ${rule_id} ---"$'\n'"${rule_body}"$'\n'
fi
if [[ "$format" == "json" ]]; then
local json_correction
json_correction="$(jq -n \
--arg id "$rule_id" \
--arg severity "$rule_severity" \
--arg body "$rule_body" \
'{id: $id, severity: $severity, body: $body}')"
if [[ "$matched" -eq 1 ]]; then
corrections="[${json_correction}"
else
corrections="${corrections},${json_correction}"
fi
else
local severity_upper
severity_upper="$(printf '%s' "$rule_severity" | tr '[:lower:]' '[:upper:]')"
corrections="${corrections}--- [${severity_upper}] Rule: ${rule_id} ---$\n'"${rule_body}"$\n'"
fi
References
  1. In shell scripts, use jq --arg for strings and --argjson for other JSON types (like numbers) to safely pass variables into a jq filter. This avoids syntax errors if the variables contain special characters.

# =============================================================================

log_error() {
local msg="$1"

Choose a reason for hiding this comment

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

medium

The repository style guide requires declaring and assigning local variables from function arguments in separate steps to ensure exit code safety. This should be applied here and in other functions throughout the script that accept arguments.

Suggested change
local msg="$1"
local msg
msg="$1"
References
  1. The style guide specifies that local variables from function arguments should be declared and assigned separately (e.g., local var; var="$1") for exit code safety. (link)
  2. When declaring and assigning a local variable in shell scripts, separate the 'local' declaration from the assignment if command substitution is involved, to ensure exit code safety.
  3. When declaring and assigning local variables in shell scripts, separate the local declaration from the assignment if command substitution is involved, to ensure exit code safety. For consistency, this pattern should be maintained even for simple assignments.

fi

local result
result="$(grep "^${rule_id}:" "$state_file" 2>/dev/null | tail -1 | cut -d: -f2)" || true

Choose a reason for hiding this comment

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

medium

The use of 2>/dev/null with grep suppresses all error messages, which can hide issues like a malformed state file or permission errors. Since || true is already used to handle the case where grep finds no matches, the 2>/dev/null is not necessary for that purpose and can be safely removed to improve error visibility.

Suggested change
result="$(grep "^${rule_id}:" "$state_file" 2>/dev/null | tail -1 | cut -d: -f2)" || true
result="$(grep "^${rule_id}:" "$state_file" | tail -1 | cut -d: -f2)" || true
References
  1. Avoid using '2>/dev/null' for blanket suppression of command errors in shell scripts to ensure that authentication, syntax, or system issues remain visible for debugging.
  2. In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.
  3. Avoid using 2>/dev/null to suppress errors on file operations if the file's existence has already been verified by a preceding check (e.g., [[ -f "$file" ]] or an early return). This practice is redundant for 'file not found' errors and can mask other important issues like permissions problems.


# Remove old entry for this rule, append new
if [[ -f "$state_file" ]]; then
grep -v "^${rule_id}:" "$state_file" >"${state_file}.tmp" 2>/dev/null || true

Choose a reason for hiding this comment

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

medium

Using 2>/dev/null here hides potential errors from grep, such as problems reading the state file. The || true guard is sufficient to prevent the script from exiting if no lines are filtered. Removing the redirection will make the script more robust by revealing underlying file system or permission issues.

Suggested change
grep -v "^${rule_id}:" "$state_file" >"${state_file}.tmp" 2>/dev/null || true
grep -v "^${rule_id}:" "$state_file" >"${state_file}.tmp" || true
References
  1. Avoid using '2>/dev/null' for blanket suppression of command errors in shell scripts to ensure that authentication, syntax, or system issues remain visible for debugging.
  2. In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.
  3. Avoid using 2>/dev/null to suppress errors on file operations if the file's existence has already been verified by a preceding check (e.g., [[ -f "$file" ]] or an early return). This practice is redundant for 'file not found' errors and can mask other important issues like permissions problems.

coderabbitai[bot]
coderabbitai bot previously requested changes Feb 22, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
.agents/rules/README.md (1)

46-46: Testing commands should specify the script path.

Users following this guide may not have ttsr-rule-loader.sh on their PATH. Consider showing the relative path from the repo root.

📝 Suggested clarification
-4. Test with: `ttsr-rule-loader.sh list` and `ttsr-rule-loader.sh check <file>`
+4. Test with: `.agents/scripts/ttsr-rule-loader.sh list` and `echo "test text" | .agents/scripts/ttsr-rule-loader.sh check -`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/rules/README.md at line 46, Update the README's testing commands to
show the script path so users not on PATH can run them; replace
"ttsr-rule-loader.sh list" and "ttsr-rule-loader.sh check <file>" with the
relative-path forms (for example "./ttsr-rule-loader.sh list" and
"./ttsr-rule-loader.sh check <file>") to make the invocation explicit from the
repo root.
.agents/scripts/ttsr-rule-loader.sh (1)

265-278: State lookup uses unanchored regex with rule_id — consider grep -F for robustness.

grep "^${rule_id}:" treats rule_id as a BRE pattern. Current rule IDs (alphanumeric + hyphens) are safe, but if a future rule ID ever contains . or other regex metacharacters, it could match unintended state entries. Since you already have the : delimiter, a fixed-string match with post-filter would be more defensive.

♻️ Safer fixed-string alternative
-	result="$(grep "^${rule_id}:" "$state_file" 2>/dev/null | tail -1 | cut -d: -f2)" || true
+	result="$(grep -F "${rule_id}:" "$state_file" 2>/dev/null | grep "^${rule_id}:" | tail -1 | cut -d: -f2)" || true

Or use awk for exact field matching:

-	result="$(grep "^${rule_id}:" "$state_file" 2>/dev/null | tail -1 | cut -d: -f2)" || true
+	result="$(awk -F: -v id="$rule_id" '$1 == id { val=$2 } END { if (val) print val }' "$state_file" 2>/dev/null)" || true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/ttsr-rule-loader.sh around lines 265 - 278, The
get_last_fired function uses grep "^${rule_id}:" which treats rule_id as a
regex; change the lookup to a fixed-string/field-aware match to avoid accidental
regex metacharacter matches. Replace the grep pipeline that sets result
("result="$(grep "^${rule_id}:" "$state_file" ... | tail -1 | cut -d: -f2)")
with an awk-based lookup that uses -F: and matches the first field exactly (use
-v id="$rule_id" '$1==id {print $2}' on "$state_file" and then tail -1) so
rule_id and state_file are handled safely and robustly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.agents/rules/no-hardcoded-secrets.md:
- Line 3: Update the ttsr_trigger regex to use POSIX character class [[:space:]]
instead of \s for portability: locate the ttsr_trigger definition (the pattern
string
"(api[_-]?key|password|secret|token)\s*[:=]\s*['\"][A-Za-z0-9+/=_-]{16,}") and
replace each \s with [[:space:]] so the pattern becomes portable in ERE on
macOS/BSD while preserving the same optional-space qualifiers around the
separator and quotes.

In @.agents/rules/no-todo-edit-by-worker.md:
- Line 3: The ttsr_trigger regex currently lets the second and third alternates
match without the Edit|Write guard; update the ttsr_trigger so the file path
alternatives are grouped together after the initial Edit|Write.* portion (e.g.,
wrap the paths in a single parenthesized group or non-capturing group) so that
Edit|Write applies to all three branches (references: ttsr_trigger).

In @.agents/scripts/ttsr-rule-loader.sh:
- Around line 546-552: The --turn case currently assigns current_turn without
validation; add a positive-integer check for the provided value (the argument
currently read into "$2") before setting current_turn so subsequent arithmetic
(e.g., in cmd_check where current_turn is used in $((current_turn -
last_fired))) cannot fail. Implement a guard using a numeric regex (e.g.,
^[0-9]+$) and ensure the value is >0; if the check fails call log_error with a
clear message and invoke usage/exit, otherwise set current_turn and shift 2 as
before. Make sure to reference the same --turn case handling and the
current_turn variable so reviewers can locate the change.
- Around line 367-371: The JSON output is invalid because rule_trigger (and
potentially rule_tags/rule_file) are interpolated raw; extract the existing awk
JSON escaper used in cmd_check (lines ~436–444) into a reusable shell function
(e.g., json_escape()) and call it in cmd_list to escape rule_trigger (and
rule_tags, rule_file) before printing; then replace direct interpolations
"$rule_trigger" "$rule_tags" "$rule_file" in the printf with the escaped
variables so the JSON strings contain properly backslash-escaped backslashes and
quotes.

---

Nitpick comments:
In @.agents/rules/README.md:
- Line 46: Update the README's testing commands to show the script path so users
not on PATH can run them; replace "ttsr-rule-loader.sh list" and
"ttsr-rule-loader.sh check <file>" with the relative-path forms (for example
"./ttsr-rule-loader.sh list" and "./ttsr-rule-loader.sh check <file>") to make
the invocation explicit from the repo root.

In @.agents/scripts/ttsr-rule-loader.sh:
- Around line 265-278: The get_last_fired function uses grep "^${rule_id}:"
which treats rule_id as a regex; change the lookup to a fixed-string/field-aware
match to avoid accidental regex metacharacter matches. Replace the grep pipeline
that sets result ("result="$(grep "^${rule_id}:" "$state_file" ... | tail -1 |
cut -d: -f2)") with an awk-based lookup that uses -F: and matches the first
field exactly (use -v id="$rule_id" '$1==id {print $2}' on "$state_file" and
then tail -1) so rule_id and state_file are handled safely and robustly.

@@ -0,0 +1,15 @@
---
id: no-hardcoded-secrets
ttsr_trigger: (api[_-]?key|password|secret|token)\s*[:=]\s*['"][A-Za-z0-9+/=_-]{16,}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

macOS BSD grep -E support for \s escape sequence ERE

💡 Result:

\s is not POSIX ERE. In POSIX, most backslash escapes other than a small set are undefined, so you can’t rely on grep -E '\s' being whitespace across platforms. (gnu.org)

On macOS’s /usr/bin/grep (BSD grep) specifically:

  • macOS’s underlying regex engine documents an “enhanced” mode that adds shorthand classes such as \d, \w, \s, etc. (see re_format(7) “ENHANCED FEATURES”). (unix.com)
  • Whether those enhanced escapes work depends on whether the tool was built to enable that enhanced mode; it’s not guaranteed just because you used -E (extended). (stackoverflow.com)

Portable recommendation (works on macOS BSD grep and everywhere POSIX):

  • Use the POSIX character class instead of \s:
    • whitespace: [[:space:]]
    • non-whitespace: [^[:space:]]

BSD-vs-GNU difference summary: treating \s as whitespace is commonly a GNU grep extension, and BSD grep may not treat it that way—so prefer [[:space:]] for portability. (unix.stackexchange.com)

Citations:


🌐 Web query:

does grep -E support \s on macOS older versions

💡 Result:

No—on older (and current) macOS, the default /usr/bin/grep -E uses POSIX ERE (BSD grep), and \s is not a POSIX ERE escape (a backslash before an “ordinary” character like s is unspecified/undefined in POSIX ERE), so it typically won’t mean “whitespace.” [1]

Use POSIX character classes instead:

grep -E '[[:space:]]+'    file
grep -E '^[[:space:]]*$'  file

If you specifically need \s, you generally need PCRE, e.g. GNU grep’s PCRE mode:

ggrep -P '\s+'

…but Apple’s BSD grep does not provide -P by default; you’d install GNU grep (e.g., via Homebrew) to get it. [2]

Sources:
[1] POSIX ERE escapes: backslash before ordinary char is undefined/unspecified (POSIX ERE syntax summary) https://www.classes.cs.uchicago.edu/archive/2013/fall/51025-1/boost_1_50_0/libs/regex/doc/html/boost_regex/syntax/basic_extended.html
[2] macOS ships BSD tools by default; install GNU grep (Homebrew) to get GNU features like ggrep / -P workflows https://stackoverflow.com/questions/16658333/grep-p-no-longer-works-how-can-i-rewrite-my-searches and https://johnk.dev/articles/mac-os-tools.html


🏁 Script executed:

# Check what grep is available and test \s support
echo "test string" | grep -E '\s' 2>&1 || echo "Failed"
echo "---"
# Also check if [[:space:]] works as fallback
echo "test string" | grep -E '[[:space:]]' && echo "POSIX char class works"

Repository: marcusquinn/aidevops

Length of output: 114


Replace \s with POSIX character class for macOS compatibility.

The trigger uses \s in ERE mode, which is not POSIX standard and not guaranteed on macOS BSD grep. While some systems have enhanced regex features, this creates a portability risk where the security rule may fail silently on stricter POSIX ERE implementations, particularly on older macOS versions.

Use [[:space:]] instead to ensure consistent behavior across all systems:

Portable ERE fix
-ttsr_trigger: (api[_-]?key|password|secret|token)\s*[:=]\s*['"][A-Za-z0-9+/=_-]{16,}
+ttsr_trigger: (api[_-]?key|password|secret|token)[[:space:]]*[:=][[:space:]]*['"][A-Za-z0-9+/=_-]{16,}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ttsr_trigger: (api[_-]?key|password|secret|token)\s*[:=]\s*['"][A-Za-z0-9+/=_-]{16,}
ttsr_trigger: (api[_-]?key|password|secret|token)[[:space:]]*[:=][[:space:]]*['"][A-Za-z0-9+/=_-]{16,}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/rules/no-hardcoded-secrets.md at line 3, Update the ttsr_trigger
regex to use POSIX character class [[:space:]] instead of \s for portability:
locate the ttsr_trigger definition (the pattern string
"(api[_-]?key|password|secret|token)\s*[:=]\s*['\"][A-Za-z0-9+/=_-]{16,}") and
replace each \s with [[:space:]] so the pattern becomes portable in ERE on
macOS/BSD while preserving the same optional-space qualifiers around the
separator and quotes.

@@ -0,0 +1,14 @@
---
id: no-todo-edit-by-worker
ttsr_trigger: (Edit|Write).*TODO\.md|todo/PLANS\.md|todo/tasks/
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Regex alternation precedence bug — second and third branches lack the Edit|Write guard.

ERE alternation | has lower precedence than concatenation, so this trigger:

(Edit|Write).*TODO\.md|todo/PLANS\.md|todo/tasks/

is parsed as three independent alternatives:

  1. (Edit|Write).*TODO\.md
  2. todo/PLANS\.md — matches any mention, including reads
  3. todo/tasks/ — matches any mention, including reads

Since this is severity: error + repeat_policy: always, every AI output that merely references todo/PLANS.md or todo/tasks/ will fire a STOP directive — even for read-only operations.

🐛 Fix: Group the file paths inside the Edit/Write guard
-ttsr_trigger: (Edit|Write).*TODO\.md|todo/PLANS\.md|todo/tasks/
+ttsr_trigger: (Edit|Write).*(TODO\.md|todo/PLANS\.md|todo/tasks/)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/rules/no-todo-edit-by-worker.md at line 3, The ttsr_trigger regex
currently lets the second and third alternates match without the Edit|Write
guard; update the ttsr_trigger so the file path alternatives are grouped
together after the initial Edit|Write.* portion (e.g., wrap the paths in a
single parenthesized group or non-capturing group) so that Edit|Write applies to
all three branches (references: ttsr_trigger).

Comment on lines +367 to +371
if [[ "$format" == "json" ]]; then
[[ "$first" -eq 1 ]] && first=0 || printf ',\n'
printf ' {"id":"%s","trigger":"%s","severity":"%s","repeat_policy":"%s","gap_turns":%s,"enabled":%s,"tags":"%s","file":"%s"}' \
"$rule_id" "$rule_trigger" "$rule_severity" "$rule_repeat_policy" \
"$rule_gap_turns" "$rule_enabled" "$rule_tags" "$rule_file"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

JSON output produces invalid JSON — rule_trigger is not escaped.

Rule triggers contain ERE regex with backslashes (\b, \., \s) that are interpolated raw into JSON strings. This produces:

  • Semantically wrong values: \b is a valid JSON escape for backspace, not a literal \b
  • Invalid JSON: \. and \s are not valid JSON escape sequences — strict parsers will reject

The rule_body gets proper awk-based escaping in cmd_check (line 436–444), but rule_trigger (and other fields) in cmd_list do not.

🐛 Proposed fix — apply the same awk escaping to trigger (and reuse it)

Extract the awk escaper into a reusable function and apply it to all fields that may contain backslashes or quotes:

+# Escape a string for safe JSON embedding
+json_escape() {
+	printf '%s' "$1" | awk '
+		BEGIN { ORS="" }
+		{
+			gsub(/\\/, "\\\\")
+			gsub(/"/, "\\\"")
+			if (NR > 1) printf "\\n"
+			printf "%s", $0
+		}
+	'
+}
+

Then in cmd_list, escape the trigger before interpolation:

+			local escaped_trigger
+			escaped_trigger="$(json_escape "$rule_trigger")"
 			printf '  {"id":"%s","trigger":"%s","severity":"%s","repeat_policy":"%s","gap_turns":%s,"enabled":%s,"tags":"%s","file":"%s"}' \
-				"$rule_id" "$rule_trigger" "$rule_severity" "$rule_repeat_policy" \
+				"$rule_id" "$escaped_trigger" "$rule_severity" "$rule_repeat_policy" \
 				"$rule_gap_turns" "$rule_enabled" "$rule_tags" "$rule_file"

Also apply to rule_tags and rule_file if they could contain special characters.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [[ "$format" == "json" ]]; then
[[ "$first" -eq 1 ]] && first=0 || printf ',\n'
printf ' {"id":"%s","trigger":"%s","severity":"%s","repeat_policy":"%s","gap_turns":%s,"enabled":%s,"tags":"%s","file":"%s"}' \
"$rule_id" "$rule_trigger" "$rule_severity" "$rule_repeat_policy" \
"$rule_gap_turns" "$rule_enabled" "$rule_tags" "$rule_file"
if [[ "$format" == "json" ]]; then
[[ "$first" -eq 1 ]] && first=0 || printf ',\n'
local escaped_trigger
escaped_trigger="$(json_escape "$rule_trigger")"
printf ' {"id":"%s","trigger":"%s","severity":"%s","repeat_policy":"%s","gap_turns":%s,"enabled":%s,"tags":"%s","file":"%s"}' \
"$rule_id" "$escaped_trigger" "$rule_severity" "$rule_repeat_policy" \
"$rule_gap_turns" "$rule_enabled" "$rule_tags" "$rule_file"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/ttsr-rule-loader.sh around lines 367 - 371, The JSON output
is invalid because rule_trigger (and potentially rule_tags/rule_file) are
interpolated raw; extract the existing awk JSON escaper used in cmd_check (lines
~436–444) into a reusable shell function (e.g., json_escape()) and call it in
cmd_list to escape rule_trigger (and rule_tags, rule_file) before printing; then
replace direct interpolations "$rule_trigger" "$rule_tags" "$rule_file" in the
printf with the escaped variables so the JSON strings contain properly
backslash-escaped backslashes and quotes.

Comment on lines +546 to +552
--turn)
[[ $# -lt 2 ]] && {
log_error "--turn requires a value"
usage
}
current_turn="$2"
shift 2
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

--turn value is not validated as a positive integer.

If a non-numeric value is passed (e.g., --turn abc), the $((current_turn - last_fired)) arithmetic on line 322 will produce a Bash error. Under set -e, this could terminate the script unexpectedly during cmd_check.

🛡️ Add integer validation
 		--turn)
 			[[ $# -lt 2 ]] && {
 				log_error "--turn requires a value"
 				usage
 			}
+			if ! [[ "$2" =~ ^[0-9]+$ ]]; then
+				log_error "--turn must be a positive integer"
+				usage
+			fi
 			current_turn="$2"
 			shift 2
 			;;

As per coding guidelines: .agents/scripts/*.sh — "Reliability and robustness" and "Error recovery mechanisms."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
--turn)
[[ $# -lt 2 ]] && {
log_error "--turn requires a value"
usage
}
current_turn="$2"
shift 2
--turn)
[[ $# -lt 2 ]] && {
log_error "--turn requires a value"
usage
}
if ! [[ "$2" =~ ^[0-9]+$ ]]; then
log_error "--turn must be a positive integer"
usage
fi
current_turn="$2"
shift 2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/ttsr-rule-loader.sh around lines 546 - 552, The --turn case
currently assigns current_turn without validation; add a positive-integer check
for the provided value (the argument currently read into "$2") before setting
current_turn so subsequent arithmetic (e.g., in cmd_check where current_turn is
used in $((current_turn - last_fired))) cannot fail. Implement a guard using a
numeric regex (e.g., ^[0-9]+$) and ensure the value is >0; if the check fails
call log_error with a clear message and invoke usage/exit, otherwise set
current_turn and shift 2 as before. Make sure to reference the same --turn case
handling and the current_turn variable so reviewers can locate the change.

@marcusquinn marcusquinn dismissed coderabbitai[bot]’s stale review February 22, 2026 07:48

Auto-dismissed: bot review does not block autonomous pipeline

@marcusquinn marcusquinn merged commit a172554 into main Feb 22, 2026
23 checks passed
@marcusquinn marcusquinn deleted the feature/t1303 branch February 22, 2026 07:49
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.

1 participant