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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Fixed

- **`hookify/rule_engine.py`**: Fixed PreToolUse deny putting message in `systemMessage` (user-only display) instead of `permissionDecisionReason` (the field Claude actually receives). Root cause of Claude never seeing hook guidance when tools are blocked. Also split PreToolUse/PostToolUse into separate branches with correct response formats per Claude Code hooks spec
- **`hookify/hook_runner.py`**: Fixed error reporting — hookify crashes now use exit code 2 + stderr (Claude-visible) instead of `systemMessage` (user-only)
- **`hookify/global-rules/mtp-smart-test-filtering`**: Rewrote rule message to be directive — shorter (8 lines vs 80) with explicit "pick one and retry" action items
- **`metacognitive-guard/epistemic-guard.sh`**: Migrated all 5 blocks from deprecated `decision`/`reason` format to modern `hookSpecificOutput.permissionDecision`/`permissionDecisionReason`. Removed dead `message` field that was ignored by Claude Code
- **`metacognitive-guard/integrity-check.sh`**: Fixed exit code 1 (non-blocking, commit proceeds) → exit code 2 (blocking, stderr fed to Claude). Violations now actually block commits instead of being silently ignored
- **`metacognitive-guard/struggle-detector`**: Split into two hooks — async Stop (analysis + blackboard write) and new `struggle-inject.sh` on UserPromptSubmit (reads blackboard, injects `additionalContext` that Claude actually sees). Removed dead `systemMessage` output from Stop hook

### Added

- **`plugins/council`**: New plugin — five-agent council (opus-captain, sonnet-researcher, sonnet-synthesizer, sonnet-clarity, haiku-janitor). Each agent identity inlined directly in its `agents/*.md` file as passive context. Researcher + synthesizer run in parallel; clarity reads their raw output; haiku-janitor flags bloat; captain removes cuts. Inspired by Grok 4.20's multi-agent architecture. Invoke via `/council [task]`.
Expand Down
2 changes: 1 addition & 1 deletion plugins/hookify/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ Rules loaded from TWO locations:
## Key Behavior

- Hades permit exemption: active `.smart/delete-permit.json` bypasses ALL blocking rules.
- Python hooks read stdin JSON, return JSON with `systemMessage` or `block` action.
- Python hooks read stdin JSON, return JSON with event-specific fields: `permissionDecisionReason` (PreToolUse deny), `reason` (Stop/PostToolUse block), `additionalContext` (warnings on PostToolUse/UserPromptSubmit/SessionStart). `systemMessage` is user-display only — Claude never sees it.
- Conversation analyzer searches for "Don't use X", frustrated reactions, repeated issues.
12 changes: 8 additions & 4 deletions plugins/hookify/global-rules/hookify.mtp-smart-test.local.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ conditions:
pattern: "# VERIFY"
---

Full test suite detected. If previous tests failed, filter to just those tests.
This hookify rule blocked a full test suite run. This is intentional, not a bug.

**Bypass:** append `# VERIFY` to the command.
**Filter:** `dotnet test --filter-method "*FailingTestName"`
**Help:** use `dotnet-mtp-advisor` agent for MTP filter syntax.
**Action required — pick one:**

1. **Verify all tests pass** → Re-run the exact same command with `# VERIFY` appended
2. **Run only failing tests** → Use `--filter-method "*FailingTestName"` or `--filter-class "*ClassName"`
3. **Need MTP filter help** → Spawn the `dotnet-mtp-advisor` agent

Do NOT tell the user this is a bug. Do NOT tell the user to run the command themselves. Just pick option 1 or 2 and retry.
12 changes: 6 additions & 6 deletions plugins/hookify/hookify/core/hook_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ def run_hook(hook_event_name: str, fixed_event: Optional[str] = None):
from hookify.core.config_loader import load_rules
from hookify.core.rule_engine import RuleEngine
except ImportError as e:
print(json.dumps({"systemMessage": f"Hookify import error: {e}"}), file=sys.stdout)
sys.exit(0)
# stderr with exit 2 ensures Claude sees the error on PreToolUse
print(f"Hookify import error: {e}", file=sys.stderr)
sys.exit(2)

try:
input_data = json.load(sys.stdin)
Expand All @@ -51,7 +52,6 @@ def run_hook(hook_event_name: str, fixed_event: Optional[str] = None):
print(json.dumps(result), file=sys.stdout)

except Exception as e:
print(json.dumps({"systemMessage": f"Hookify error: {str(e)}"}), file=sys.stdout)

finally:
sys.exit(0)
# stderr with exit 2 ensures Claude sees the error on PreToolUse
print(f"Hookify error: {str(e)}", file=sys.stderr)
sys.exit(2)
46 changes: 33 additions & 13 deletions plugins/hookify/hookify/core/rule_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ def evaluate_rules(self, rules: List[Rule], input_data: Dict[str, Any]) -> Dict[
input_data: Hook input JSON (tool_name, tool_input, etc.)

Returns:
Response dict with systemMessage, hookSpecificOutput, etc.
Response dict with event-appropriate fields per Claude Code hooks spec.
Uses permissionDecisionReason (PreToolUse), reason (Stop/PostToolUse),
additionalContext (warnings on PostToolUse/UserPromptSubmit/SessionStart).
Empty dict {} if no rules match.
"""
# Hades god mode — active permit bypasses all rules
Expand All @@ -99,29 +101,47 @@ def evaluate_rules(self, rules: List[Rule], input_data: Dict[str, Any]) -> Dict[
if hook_event == 'Stop':
return {
"decision": "block",
"reason": combined_message,
"systemMessage": combined_message
"reason": combined_message
}
elif hook_event in ['PreToolUse', 'PostToolUse']:
elif hook_event == 'PreToolUse':
return {
"hookSpecificOutput": {
"hookEventName": hook_event,
"permissionDecision": "deny"
},
"systemMessage": combined_message
"permissionDecision": "deny",
"permissionDecisionReason": combined_message
}
}
else:
# For other events, just show message
elif hook_event == 'PostToolUse':
return {
"decision": "block",
"reason": combined_message
}
elif hook_event == 'UserPromptSubmit':
return {
"systemMessage": combined_message
"decision": "block",
"reason": combined_message
}
else:
# SessionStart, Notification — no blocking mechanism, no audience
return {}

# If only warnings, show them but allow operation
if warning_rules:
messages = [f"**[{r.name}]**\n{r.message}" for r in warning_rules]
return {
"systemMessage": "\n\n".join(messages)
}
combined_warning = "\n\n".join(messages)

# Use additionalContext where Claude can see it
if hook_event in ['PostToolUse', 'UserPromptSubmit', 'SessionStart']:
return {
"hookSpecificOutput": {
"hookEventName": hook_event,
"additionalContext": combined_warning
}
}
else:
# PreToolUse warnings can't reach Claude without blocking
# No audience for the message — skip
return {}
Comment on lines +133 to +144
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

action: warn rules for PreToolUse are currently dropped (function returns {} for warnings when hook_event is PreToolUse). This makes warnings effectively no-op for the most common hook path, and contradicts the documented "warn but allow" behavior. Consider emitting a user-visible warning (e.g., via systemMessage) or, if supported by the Claude Code hooks spec, returning a PreToolUse hookSpecificOutput with permissionDecision: "allow" plus a reason field so Claude can see the warning without blocking.

Copilot uses AI. Check for mistakes.

# No matches - allow operation
return {}
Expand Down
5 changes: 3 additions & 2 deletions plugins/metacognitive-guard/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ agents amplify thinking. Absorbs completion-integrity and autonomous-ci.
| Truth Beacon | SessionStart | `truth-beacon.sh` | Injects `blackboard/assertions.yaml` as authoritative facts |
| Epistemic Guard | PreToolUse (Write/Edit) | `epistemic-guard.sh` | Blocks writes with wrong versions, banned APIs, AGENTS.md in plugins |
| Commit Integrity | PreToolUse (Bash) | `commit-integrity-hook.sh` | Blocks `git commit` with suppressions, commented tests, deleted assertions |
| Struggle Detector | Stop (async) | `struggle-detector.sh` | Scores response for uncertainty, triggers deep-think suggestion |
| Struggle Detector | Stop (async) | `struggle-detector.sh` | Scores response for uncertainty, writes to blackboard |
| Struggle Inject | UserPromptSubmit | `struggle-inject.sh` | Reads blackboard, injects deep-think suggestion as `additionalContext` |
| Ralph Loop | PostToolUse (Write/Edit) | prompt (haiku) + `ralph-loop.sh` | Two-layer drift detection: haiku analyzes context (over-engineering, complexity, premature optimization), grep catches surface patterns (TODO, suppressions, catch-all). Both inject via additionalContext. Silent when clean |
| Task Completion Gate | TaskCompleted | prompt (haiku) | Validates task completions in team workflows aren't premature |

Expand Down Expand Up @@ -52,7 +53,7 @@ agents amplify thinking. Absorbs completion-integrity and autonomous-ci.
- verify-local.sh and wait-for-ci.sh are utility scripts for the verification workflow, not hook triggers.
- Hades god mode: active delete permit causes epistemic-guard to exit early.
- Struggle detector tracks consecutive struggling responses via `.blackboard/.struggle-count`.
- Struggle detector runs async (non-blocking) — feedback delivered next turn, never delays responses.
- Struggle detector is a two-part system: Stop hook (async) does analysis + blackboard writes, UserPromptSubmit hook reads blackboard and injects `additionalContext` so Claude actually sees the suggestion. No latency on responses.
- TaskCompleted prompt hook fires on every task completion in team contexts (haiku, 15s timeout).
- Ralph Loop fires PostToolUse on Write/Edit — two layers run in parallel:
(1) Haiku prompt analyzes context for deep drift (over-engineering, complexity creep, premature
Expand Down
12 changes: 12 additions & 0 deletions plugins/metacognitive-guard/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/struggle-inject.sh",
"timeout": 3,
"statusMessage": "Checking struggle state..."
}
]
}
],
"Stop": [
{
"hooks": [
Expand Down
40 changes: 25 additions & 15 deletions plugins/metacognitive-guard/hooks/scripts/epistemic-guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ fi
if [[ "$FILE_PATH" == */plugins/*/AGENTS.md ]]; then
cat << 'EOF'
{
"decision": "block",
"reason": "EPISTEMIC GUARD: Anti-pattern - AGENTS.md in plugin directory",
"message": "AGENTS.md is NOT auto-loaded by Claude Code plugins.\n\nRouting intelligence belongs in skill description frontmatter (passive context).\nUse the Vercel pattern: encode WHEN to use each skill in the description field.\n\nIf you need human documentation, use README.md instead."
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "EPISTEMIC GUARD: Anti-pattern - AGENTS.md in plugin directory.\n\nAGENTS.md is NOT auto-loaded by Claude Code plugins.\nRouting intelligence belongs in skill description frontmatter (passive context).\nUse the Vercel pattern: encode WHEN to use each skill in the description field.\nIf you need human documentation, use README.md instead."
}
}
EOF
exit 0
Expand All @@ -73,9 +75,11 @@ esac
if echo "$CONTENT" | grep -qiE "\.NET 10.*(preview|not.*(released|LTS|available))|\.NET 10 is still|net9\.0"; then
cat << 'EOF'
{
"decision": "block",
"reason": "EPISTEMIC GUARD: Incorrect .NET version claim",
"message": "FACT CHECK: .NET 10 is LTS (Long Term Support) since November 11, 2025.\n\nIt is NOT preview. Use net10.0 in TargetFramework.\n\nSource: https://dotnet.microsoft.com/download/dotnet/10.0\n\nPlease correct your response before writing."
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "EPISTEMIC GUARD: Incorrect .NET version claim. FACT: .NET 10 is LTS since November 11, 2025. It is NOT preview. Use net10.0 in TargetFramework. Correct your content before writing."
}
}
EOF
exit 0
Expand All @@ -88,9 +92,11 @@ if [[ "$IS_DOCS" == false ]]; then
if echo "$CONTENT" | grep -qE 'DateTime\.(Now|UtcNow)|DateTimeOffset\.(Now|UtcNow)'; then
cat << 'EOF'
{
"decision": "block",
"reason": "EPISTEMIC GUARD: Banned API - DateTime.Now",
"message": "DateTime.Now/UtcNow should be avoided.\n\nUse: TimeProvider.System.GetUtcNow()\n\nThis enables testability and follows .NET 8+ best practices."
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "EPISTEMIC GUARD: Banned API - DateTime.Now/UtcNow. Use TimeProvider.System.GetUtcNow() instead. This enables testability and follows .NET 8+ best practices."
}
}
EOF
exit 0
Expand All @@ -100,9 +106,11 @@ fi
if echo "$CONTENT" | grep -qE 'object\s+_?lock\s*='; then
cat << 'EOF'
{
"decision": "block",
"reason": "EPISTEMIC GUARD: Banned pattern - object lock",
"message": "object-based locking should be avoided.\n\nUse: Lock _lock = new();\n\n.NET 9+ Lock type is more efficient and type-safe."
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "EPISTEMIC GUARD: Banned pattern - object lock. Use Lock _lock = new() instead. .NET 9+ Lock type is more efficient and type-safe."
}
}
EOF
exit 0
Expand All @@ -112,9 +120,11 @@ fi
if echo "$CONTENT" | grep -qE 'Newtonsoft\.Json|JsonConvert\.'; then
cat << 'EOF'
{
"decision": "block",
"reason": "EPISTEMIC GUARD: Banned dependency - Newtonsoft.Json",
"message": "Newtonsoft.Json should be avoided in new code.\n\nUse: System.Text.Json with source generators.\n\nExample: JsonSerializer.Serialize(obj, MyContext.Default.MyType)"
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "EPISTEMIC GUARD: Banned dependency - Newtonsoft.Json. Use System.Text.Json with source generators instead. Example: JsonSerializer.Serialize(obj, MyContext.Default.MyType)"
}
}
EOF
exit 0
Expand Down
11 changes: 7 additions & 4 deletions plugins/metacognitive-guard/hooks/scripts/integrity-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,11 @@ elif [[ "$VIOLATION_COUNT" -eq 0 ]]; then
echo -e "${YELLOW}WARNINGS|$WARNING_COUNT warnings (review recommended)${NC}"
exit 0
else
echo -e "${RED}BLOCKED|$VIOLATION_COUNT violations, $WARNING_COUNT warnings${NC}"
echo ""
echo "Fix violations before committing. These patterns indicate shortcuts that will cause problems later."
exit 1
# Exit code 2 = blocking error, stderr is fed to Claude
echo "COMMIT INTEGRITY: $VIOLATION_COUNT violations found. Fix these before committing:" >&2
for v in "${VIOLATIONS[@]}"; do
echo " - $v" >&2
done
echo "These patterns indicate shortcuts that will cause problems later." >&2
Comment on lines +206 to +211
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

In the blocking path, the hook relies on exit 2 + stderr for Claude-visible output, but it currently only prints the compact VIOLATIONS[@] entries to stderr. The more detailed context emitted earlier in the script goes to stdout and may not be visible to Claude when blocking. Consider writing the key violation details/locations to stderr as well (or redirecting stdout to stderr in the violation case) so Claude gets actionable guidance.

Copilot uses AI. Check for mistakes.
exit 2
fi
21 changes: 7 additions & 14 deletions plugins/metacognitive-guard/hooks/scripts/struggle-detector.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ fi
if [[ "$score" -gt 10 ]]; then
PREV_COUNT=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0")
NEW_COUNT=$((PREV_COUNT + 1))
# High severity (score > 25) triggers inject on next prompt immediately
if [[ "$score" -gt 25 && "$NEW_COUNT" -lt 2 ]]; then
NEW_COUNT=2
fi
echo "$NEW_COUNT" > "$STRUGGLE_COUNT_FILE"
else
echo "0" > "$STRUGGLE_COUNT_FILE"
Expand All @@ -164,19 +168,8 @@ CONSECUTIVE=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0")
done
} >> "$STRUGGLE_FILE"

# Trigger after 2+ consecutive struggling responses OR high single score
if [[ "$CONSECUTIVE" -ge 2 ]] || [[ "$score" -gt 25 ]]; then
# Build signals list with proper escaping
SIGNALS_LIST=""
for sig in "${signals[@]}"; do
SIGNALS_LIST="${SIGNALS_LIST}- ${sig}\\n"
done

cat <<EOF
{
"systemMessage": "<struggle-detected score=\"$score\" consecutive=\"$CONSECUTIVE\">\\n\\nClaude appears to be struggling with this problem.\\n\\nSignals detected:\\n${SIGNALS_LIST}\\nSuggestion: Use the Task tool with subagent_type='deep-think-partner' for thorough analysis.\\n\\nExample: \\\"I'm finding this complex. Want me to spawn a deep-thinker for a more thorough analysis?\\\"\\n</struggle-detected>"
}
EOF
fi
# Note: No JSON output needed here. The struggle-inject.sh (UserPromptSubmit)
# reads .blackboard/.struggle-count and injects additionalContext to Claude
# on the next prompt. This async Stop hook only does analysis + blackboard writes.
Comment on lines +171 to +173
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

This section now intentionally produces no JSON output, but the surrounding section header/comments still describe "OUTPUT SUGGESTION IF THRESHOLD MET". Updating the header/comments to match the new behavior will avoid confusion when maintaining the hook.

Copilot uses AI. Check for mistakes.

exit 0
43 changes: 43 additions & 0 deletions plugins/metacognitive-guard/hooks/scripts/struggle-inject.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# =============================================================================
# STRUGGLE INJECT - Delivers struggle detection to Claude via UserPromptSubmit
# =============================================================================
# Trigger: UserPromptSubmit (before Claude processes the next prompt)
# Purpose: Read blackboard state from async struggle-detector and inject
# suggestion as additionalContext so Claude actually sees it.
#
# The struggle-detector.sh (Stop, async) writes scoring data to:
# .blackboard/.struggle-count — consecutive struggling responses
# .blackboard/.struggle-signals — detailed signal log
#
# This hook reads those files and injects context when threshold is met.
# After injection, resets the counter to prevent repeated suggestions.
# =============================================================================

set -euo pipefail

PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-}"
[[ -z "$PLUGIN_ROOT" ]] && exit 0

BLACKBOARD="$PLUGIN_ROOT/.blackboard"
STRUGGLE_COUNT_FILE="$BLACKBOARD/.struggle-count"

# Check consecutive struggle count
CONSECUTIVE=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

CONSECUTIVE is read from a file and then compared with -lt. If the file content is empty or non-numeric, the comparison returns an error and the script may proceed as if threshold was met. Consider normalizing CONSECUTIVE to an integer (default 0) before the numeric comparison.

Suggested change
CONSECUTIVE=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0")
CONSECUTIVE=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0")
# Normalize to integer; default to 0 if empty or non-numeric
if ! [[ "$CONSECUTIVE" =~ ^[0-9]+$ ]]; then
CONSECUTIVE=0
fi

Copilot uses AI. Check for mistakes.
[[ "$CONSECUTIVE" -lt 2 ]] && exit 0
Comment on lines +26 to +27

Choose a reason for hiding this comment

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

P2 Badge Preserve single-response struggle escalation

struggle-inject.sh now injects guidance only when .struggle-count is >= 2, but before this change the detector escalated on either two consecutive struggles or one very high score (score > 25). With the new split, a severe one-off struggle increments the count to 1 and no suggestion is injected on the next prompt unless a second bad response occurs, so the high-severity path was unintentionally removed.

Useful? React with 👍 / 👎.


# Read recent signals for context
SIGNALS_FILE="$BLACKBOARD/.struggle-signals"
RECENT_SIGNALS=""
if [[ -f "$SIGNALS_FILE" ]]; then
RECENT_SIGNALS=$( (tail -20 "$SIGNALS_FILE" | grep ' - ' || true) | sed 's/ - //' | tr '\n' ', ' | sed 's/, $//')
fi

# Inject as additionalContext — use jq for safe JSON construction
MSG="STRUGGLE DETECTOR ($CONSECUTIVE consecutive uncertain responses): Signals — $RECENT_SIGNALS. Consider spawning a deep-think-partner agent (Task tool, subagent_type='deep-think-partner') for thorough analysis instead of continuing to iterate."
jq -n --arg ctx "$MSG" '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":$ctx}}'
Comment on lines +36 to +38
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

struggle-inject.sh assumes jq is available. If jq is missing, the script will exit non-zero under set -e and the hook won’t inject any additionalContext. Other hooks in this plugin (e.g., truth-beacon.sh) include a jq/no-jq fallback—consider adding the same here so the hook degrades gracefully.

Suggested change
# Inject as additionalContext — use jq for safe JSON construction
MSG="STRUGGLE DETECTOR ($CONSECUTIVE consecutive uncertain responses): Signals — $RECENT_SIGNALS. Consider spawning a deep-think-partner agent (Task tool, subagent_type='deep-think-partner') for thorough analysis instead of continuing to iterate."
jq -n --arg ctx "$MSG" '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":$ctx}}'
# Inject as additionalContext — prefer jq for safe JSON construction, but degrade gracefully if missing
MSG="STRUGGLE DETECTOR ($CONSECUTIVE consecutive uncertain responses): Signals — $RECENT_SIGNALS. Consider spawning a deep-think-partner agent (Task tool, subagent_type='deep-think-partner') for thorough analysis instead of continuing to iterate."
if command -v jq >/dev/null 2>&1; then
jq -n --arg ctx "$MSG" '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":$ctx}}'
else
# Fallback: emit JSON using basic shell escaping when jq is not available
ESCAPED_MSG=$(printf '%s' "$MSG" | sed 's/\\/\\\\/g; s/"/\\"/g')
printf '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"%s"}}\n' "$ESCAPED_MSG"
fi

Copilot uses AI. Check for mistakes.

# Reset after delivering to prevent repeated suggestions
echo "0" > "$STRUGGLE_COUNT_FILE"

exit 0
Loading