@miller-tech/uap 1.222.0 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -9
- package/config/exl3-profiles/qwen38-27b-exl3.env +68 -0
- package/config/llama-profiles/qwen38-27b-dflash2.env +6 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/benchmarks/paired/adapter.d.ts +15 -0
- package/dist/benchmarks/paired/adapter.d.ts.map +1 -1
- package/dist/benchmarks/paired/adapter.js +22 -1
- package/dist/benchmarks/paired/adapter.js.map +1 -1
- package/dist/bin/cli.js +20 -24
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/deliver.d.ts +34 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +147 -20
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/guided-setup.d.ts.map +1 -1
- package/dist/cli/guided-setup.js +15 -0
- package/dist/cli/guided-setup.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +3 -0
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/plan.d.ts.map +1 -1
- package/dist/cli/plan.js +8 -2
- package/dist/cli/plan.js.map +1 -1
- package/dist/cli/policy.d.ts.map +1 -1
- package/dist/cli/policy.js +104 -2
- package/dist/cli/policy.js.map +1 -1
- package/dist/cli/quality.d.ts +11 -0
- package/dist/cli/quality.d.ts.map +1 -0
- package/dist/cli/quality.js +213 -0
- package/dist/cli/quality.js.map +1 -0
- package/dist/cli/wizard-config.d.ts.map +1 -1
- package/dist/cli/wizard-config.js +19 -9
- package/dist/cli/wizard-config.js.map +1 -1
- package/dist/cli/worktree.d.ts.map +1 -1
- package/dist/cli/worktree.js +8 -1
- package/dist/cli/worktree.js.map +1 -1
- package/dist/dashboard/savings.d.ts +1 -4
- package/dist/dashboard/savings.d.ts.map +1 -1
- package/dist/dashboard/savings.js +2 -52
- package/dist/dashboard/savings.js.map +1 -1
- package/dist/delivery/acceptance-judge.d.ts +10 -0
- package/dist/delivery/acceptance-judge.d.ts.map +1 -1
- package/dist/delivery/acceptance-judge.js +45 -3
- package/dist/delivery/acceptance-judge.js.map +1 -1
- package/dist/delivery/agentic-executor.d.ts +12 -1
- package/dist/delivery/agentic-executor.d.ts.map +1 -1
- package/dist/delivery/agentic-executor.js +25 -5
- package/dist/delivery/agentic-executor.js.map +1 -1
- package/dist/delivery/bash-sweep.d.ts +12 -0
- package/dist/delivery/bash-sweep.d.ts.map +1 -1
- package/dist/delivery/bash-sweep.js +8 -1
- package/dist/delivery/bash-sweep.js.map +1 -1
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +6 -4
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/execution-gate.d.ts +1 -1
- package/dist/delivery/execution-gate.d.ts.map +1 -1
- package/dist/delivery/execution-gate.js +248 -5
- package/dist/delivery/execution-gate.js.map +1 -1
- package/dist/delivery/snapshot.d.ts +16 -0
- package/dist/delivery/snapshot.d.ts.map +1 -1
- package/dist/delivery/snapshot.js +113 -2
- package/dist/delivery/snapshot.js.map +1 -1
- package/dist/delivery/user-validation.d.ts.map +1 -1
- package/dist/delivery/user-validation.js +55 -10
- package/dist/delivery/user-validation.js.map +1 -1
- package/dist/delivery/verifier-ladder.d.ts +24 -0
- package/dist/delivery/verifier-ladder.d.ts.map +1 -1
- package/dist/delivery/verifier-ladder.js +119 -2
- package/dist/delivery/verifier-ladder.js.map +1 -1
- package/dist/generators/claude-md.js +17 -4
- package/dist/generators/claude-md.js.map +1 -1
- package/dist/policies/liveness.d.ts +105 -0
- package/dist/policies/liveness.d.ts.map +1 -0
- package/dist/policies/liveness.js +251 -0
- package/dist/policies/liveness.js.map +1 -0
- package/dist/policies/policy-memory.d.ts.map +1 -1
- package/dist/policies/policy-memory.js +3 -4
- package/dist/policies/policy-memory.js.map +1 -1
- package/dist/quality/baseline.d.ts +29 -0
- package/dist/quality/baseline.d.ts.map +1 -0
- package/dist/quality/baseline.js +79 -0
- package/dist/quality/baseline.js.map +1 -0
- package/dist/quality/complexity.d.ts +38 -0
- package/dist/quality/complexity.d.ts.map +1 -0
- package/dist/quality/complexity.js +239 -0
- package/dist/quality/complexity.js.map +1 -0
- package/dist/quality/config.d.ts +40 -0
- package/dist/quality/config.d.ts.map +1 -0
- package/dist/quality/config.js +77 -0
- package/dist/quality/config.js.map +1 -0
- package/dist/quality/crap.d.ts +11 -0
- package/dist/quality/crap.d.ts.map +1 -0
- package/dist/quality/crap.js +54 -0
- package/dist/quality/crap.js.map +1 -0
- package/dist/quality/languages.d.ts +25 -0
- package/dist/quality/languages.d.ts.map +1 -0
- package/dist/quality/languages.js +150 -0
- package/dist/quality/languages.js.map +1 -0
- package/dist/quality/report.d.ts +38 -0
- package/dist/quality/report.d.ts.map +1 -0
- package/dist/quality/report.js +251 -0
- package/dist/quality/report.js.map +1 -0
- package/dist/quality/scanner.d.ts +32 -0
- package/dist/quality/scanner.d.ts.map +1 -0
- package/dist/quality/scanner.js +127 -0
- package/dist/quality/scanner.js.map +1 -0
- package/dist/quality/tools.d.ts +28 -0
- package/dist/quality/tools.d.ts.map +1 -0
- package/dist/quality/tools.js +189 -0
- package/dist/quality/tools.js.map +1 -0
- package/dist/types/config.d.ts +148 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +35 -0
- package/dist/types/config.js.map +1 -1
- package/docs/ATTRIBUTION.md +0 -1
- package/docs/INDEX.md +38 -4
- package/docs/analysis/MARKET_POSITION.md +179 -0
- package/docs/architecture/OVERVIEW.md +82 -18
- package/docs/architecture/PROTOCOL.md +1 -1
- package/docs/benchmarks/PAIRED_FINDINGS.md +106 -0
- package/docs/benchmarks/PAIRED_HARNESS.md +16 -0
- package/docs/benchmarks/README.md +59 -0
- package/docs/design/UAP_REACTOR.md +0 -1
- package/docs/getting-started/INSTALLATION.md +1 -1
- package/docs/guides/AUTOMATIC.md +0 -14
- package/docs/guides/AUTOMATIC_FEATURES.md +1 -9
- package/docs/guides/COORDINATION.md +1 -1
- package/docs/guides/DELIVERY_PIPELINE.md +5 -0
- package/docs/guides/DEPLOY_BATCHING.md +1 -1
- package/docs/guides/DROIDS_AND_SKILLS.md +1 -1
- package/docs/guides/LOCAL_MODELS.md +6 -1
- package/docs/guides/MCP_ROUTER.md +1 -1
- package/docs/guides/MEMORY.md +1 -1
- package/docs/guides/MULTI_MODEL.md +1 -1
- package/docs/guides/ORCHESTRATOR.md +1 -1
- package/docs/guides/POLICIES.md +19 -3
- package/docs/guides/QWEN38_EXL3.md +114 -0
- package/docs/guides/WORKTREE_WORKFLOW.md +45 -1
- package/docs/integrations/MCP_ROUTER.md +1 -4
- package/docs/plans/deliver-hardening-plan-2026-07-13.md +173 -0
- package/docs/plans/deliver-hardening-review-response-2026-07-13.md +180 -0
- package/docs/plans/k3-uap-uplift-validation.md +216 -0
- package/docs/plans/qwen38-uap-retest-plan.md +196 -0
- package/docs/reference/API.md +1 -1
- package/docs/reference/CLI.md +49 -14
- package/docs/reference/COMPARISON.md +248 -0
- package/docs/reference/CONFIGURATION.md +1 -3
- package/docs/reference/DATABASE_SCHEMA.md +1 -1
- package/docs/reference/FEATURES.md +3 -3
- package/docs/reference/PATTERNS.md +1 -1
- package/docs/reference/PLATFORMS.md +1 -1
- package/package.json +2 -2
- package/scripts/maintenance/update-skills.py +0 -22
- package/scripts/maintenance/verify-compliance.sh +3 -3
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
- package/src/policies/enforcers/adr_guard.py +0 -0
- package/src/policies/enforcers/bearer_lockdown.py +0 -0
- package/src/policies/enforcers/branch_freshness.py +0 -0
- package/src/policies/enforcers/design_token_gate.py +0 -0
- package/src/policies/enforcers/enforcement_self_protect.py +70 -0
- package/src/policies/enforcers/expert_review_required.py +22 -0
- package/src/policies/enforcers/iac_plan_destruction_check.py +0 -0
- package/src/policies/enforcers/quality_metrics_gate.py +447 -0
- package/src/policies/enforcers/ship_loop_gate.py +0 -0
- package/src/policies/enforcers/validate_plan_on_change.py +0 -0
- package/src/policies/enforcers/visual_verification.py +0 -0
- package/src/policies/enforcers/workdir_scope.py +0 -0
- package/src/policies/schemas/policies/expert-review-required.md +9 -1
- package/src/policies/schemas/policies/quality-metrics-gate.md +74 -0
- package/templates/CLAUDE.template.md +1 -1
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/templates/hooks/pre-tool-use-bash.sh +50 -3
- package/templates/hooks/pre-tool-use-edit-write.sh +31 -0
- package/templates/hooks/session-start.sh +15 -0
- package/templates/hooks/uap-policy-gate.sh +187 -5
- package/templates/stryker.config.json +14 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +254 -28
- package/tools/agents/tests/run_acceptance.py +0 -0
- package/tools/agents/tests/test_client_disconnect.py +0 -0
- package/tools/agents/tests/test_cycle_break_exploration.py +0 -0
- package/tools/agents/tests/test_deferral_break.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_all_langs.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_exemptions.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_filepath.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_web_and_bash.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_worktree.py +0 -0
- package/tools/agents/tests/test_delivery_enforcement_write_complexity.py +0 -0
- package/tools/agents/tests/test_disconnect_watcher.py +0 -0
- package/tools/agents/tests/test_doubling_break.py +0 -0
- package/tools/agents/tests/test_empty_maxtokens_recovery.py +0 -0
- package/tools/agents/tests/test_empty_tool_loop_break.py +0 -0
- package/tools/agents/tests/test_enforcement_self_protect.py +90 -0
- package/tools/agents/tests/test_enforcer_escape_hatches.py +0 -0
- package/tools/agents/tests/test_error_loop_break.py +0 -0
- package/tools/agents/tests/test_expert_review_pr_scope.py +13 -5
- package/tools/agents/tests/test_fastpath_config.py +0 -0
- package/tools/agents/tests/test_finalize_suppression.py +0 -0
- package/tools/agents/tests/test_gate_evidence.py +0 -0
- package/tools/agents/tests/test_gate_failclosed_indirection.py +0 -0
- package/tools/agents/tests/test_gate_integrity.py +0 -0
- package/tools/agents/tests/test_live_deliver_kill.py +0 -0
- package/tools/agents/tests/test_malformed_unclosed_think.py +0 -0
- package/tools/agents/tests/test_mandate_beats_recon.py +0 -0
- package/tools/agents/tests/test_mandate_deliver.py +0 -0
- package/tools/agents/tests/test_non_stream_resp_log.py +0 -0
- package/tools/agents/tests/test_output_token_ceilings.py +0 -0
- package/tools/agents/tests/test_overflow_truncate_count_tokens.py +0 -0
- package/tools/agents/tests/test_path_containment.py +0 -0
- package/tools/agents/tests/test_path_normalizer_hardened.py +0 -0
- package/tools/agents/tests/test_repeat_call_guard.py +0 -0
- package/tools/agents/tests/test_repeat_guard.py +0 -0
- package/tools/agents/tests/test_session_admission.py +0 -0
- package/tools/agents/tests/test_stream_heartbeat.py +0 -0
- package/tools/agents/tests/test_stream_thinking_deltas.py +405 -0
- package/tools/agents/tests/test_stuck_break_hard.py +0 -0
- package/tools/agents/tests/test_stuck_break_reattach.py +0 -0
- package/tools/agents/tests/test_turn_count_breaker_periodic.py +0 -0
- package/tools/agents/tests/test_upstream_chokepoint.py +0 -0
- package/tools/agents/tests/test_upstream_model_aliases.py +0 -0
- package/tools/agents/tests/test_validate_plan_gate.py +0 -0
- package/tools/agents/tests/test_vision_passthrough.py +0 -0
- package/tools/agents/tests/test_workdir_scope_enforcer.py +0 -0
- package/dist/cli/rtk.d.ts +0 -34
- package/dist/cli/rtk.d.ts.map +0 -1
- package/dist/cli/rtk.js +0 -401
- package/dist/cli/rtk.js.map +0 -1
- package/docs/integrations/RTK.md +0 -113
- package/scripts/setup/install-rtk.sh +0 -184
- package/src/policies/enforcers/rtk_wrap.py +0 -205
- package/src/policies/schemas/policies/rtk-wrap.md +0 -50
- package/tools/agents/tests/test_rtk_wrap.py +0 -151
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# RTK (Rust Token Killer) Installation Script for UAP
|
|
3
|
-
# Auto-detects OS and installs RTK via appropriate method
|
|
4
|
-
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
echo "🔧 Installing RTK (Rust Token Killer)..."
|
|
8
|
-
echo " Reduces LLM token consumption by 60-90% on CLI commands"
|
|
9
|
-
echo ""
|
|
10
|
-
|
|
11
|
-
# Detect OS and architecture
|
|
12
|
-
detect_os() {
|
|
13
|
-
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
14
|
-
echo "macOS"
|
|
15
|
-
elif [[ "$(uname -s)" == "Linux" ]]; then
|
|
16
|
-
echo "Linux"
|
|
17
|
-
elif [[ "$(uname -s)" =~ MINGW.* || "$(uname -s)" =~ MSYS* ]]; then
|
|
18
|
-
echo "Windows"
|
|
19
|
-
else
|
|
20
|
-
echo "Unknown"
|
|
21
|
-
fi
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
detect_arch() {
|
|
25
|
-
local arch=$(uname -m)
|
|
26
|
-
case $arch in
|
|
27
|
-
x86_64) echo "x86_64" ;;
|
|
28
|
-
arm64|aarch64) echo "arm64" ;;
|
|
29
|
-
*) echo "$arch" ;;
|
|
30
|
-
esac
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
OS=$(detect_os)
|
|
34
|
-
ARCH=$(detect_arch)
|
|
35
|
-
|
|
36
|
-
echo "Detected: $OS ($ARCH)"
|
|
37
|
-
echo ""
|
|
38
|
-
|
|
39
|
-
# Check if Homebrew is available (macOS/Linux)
|
|
40
|
-
check_brew() {
|
|
41
|
-
command -v brew &> /dev/null && echo "yes" || echo "no"
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
# Check if cargo is installed
|
|
45
|
-
check_cargo() {
|
|
46
|
-
command -v cargo &> /dev/null && echo "yes" || echo "no"
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# Check if RTK is already installed
|
|
50
|
-
check_rtk_installed() {
|
|
51
|
-
command -v rtk &> /dev/null && echo "yes" || echo "no"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
RTK_INSTALLED=$(check_rtk_installed)
|
|
55
|
-
|
|
56
|
-
if [[ "$RTK_INSTALLED" == "yes" ]]; then
|
|
57
|
-
CURRENT_VERSION=$(rtk --version 2>&1 | head -1)
|
|
58
|
-
echo "ℹ RTK is already installed: $CURRENT_VERSION"
|
|
59
|
-
echo ""
|
|
60
|
-
read -p "Do you want to upgrade to the latest version? [y/N] " -n 1 -r
|
|
61
|
-
echo ""
|
|
62
|
-
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
63
|
-
echo "Skipping installation."
|
|
64
|
-
exit 0
|
|
65
|
-
fi
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
# Installation methods (in order of preference)
|
|
69
|
-
install_methods=(
|
|
70
|
-
"homebrew"
|
|
71
|
-
"cargo"
|
|
72
|
-
"curl_install"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
INSTALL_SUCCESS=false
|
|
76
|
-
|
|
77
|
-
for method in "${install_methods[@]}"; do
|
|
78
|
-
echo "📦 Trying installation method: $method"
|
|
79
|
-
|
|
80
|
-
case $method in
|
|
81
|
-
homebrew)
|
|
82
|
-
if [[ "$OS" == "macOS" ]] || (check_brew == "yes"); then
|
|
83
|
-
echo " Installing via Homebrew..."
|
|
84
|
-
if brew install rtk &> /dev/null; then
|
|
85
|
-
INSTALL_SUCCESS=true
|
|
86
|
-
break
|
|
87
|
-
fi
|
|
88
|
-
fi
|
|
89
|
-
;;
|
|
90
|
-
|
|
91
|
-
cargo)
|
|
92
|
-
if check_cargo == "yes"; then
|
|
93
|
-
echo " Installing via Cargo..."
|
|
94
|
-
if cargo install --git https://github.com/rtk-ai/rtk --quiet 2>&1; then
|
|
95
|
-
INSTALL_SUCCESS=true
|
|
96
|
-
break
|
|
97
|
-
fi
|
|
98
|
-
fi
|
|
99
|
-
;;
|
|
100
|
-
|
|
101
|
-
curl_install)
|
|
102
|
-
if [[ "$OS" == "macOS" ]] || [[ "$OS" == "Linux" ]]; then
|
|
103
|
-
echo " Installing via curl..."
|
|
104
|
-
TEMP_DIR=$(mktemp -d)
|
|
105
|
-
cd "$TEMP_DIR"
|
|
106
|
-
|
|
107
|
-
# Download appropriate binary based on OS/arch
|
|
108
|
-
if [[ "$ARCH" == "arm64" ]]; then
|
|
109
|
-
BINARY="rtk-aarch64-apple-darwin.tar.gz"
|
|
110
|
-
else
|
|
111
|
-
BINARY="rtk-x86_64-apple-darwin.tar.gz"
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
|
-
if [[ "$OS" == "Linux" ]]; then
|
|
115
|
-
if [[ "$ARCH" == "arm64" ]]; then
|
|
116
|
-
BINARY="rtk-aarch64-unknown-linux-gnu.tar.gz"
|
|
117
|
-
else
|
|
118
|
-
BINARY="rtk-x86_64-unknown-linux-musl.tar.gz"
|
|
119
|
-
fi
|
|
120
|
-
fi
|
|
121
|
-
|
|
122
|
-
echo " Downloading: $BINARY"
|
|
123
|
-
|
|
124
|
-
if curl -fsSL "https://github.com/rtk-ai/rtk/releases/latest/download/$BINARY" -o rtk.tar.gz 2>&1; then
|
|
125
|
-
tar -xzf rtk.tar.gz
|
|
126
|
-
chmod +x rtk
|
|
127
|
-
|
|
128
|
-
# Move to local bin or PATH
|
|
129
|
-
if [[ -d "$HOME/.local/bin" ]] || echo "$PATH" | grep -q "$HOME/.local/bin"; then
|
|
130
|
-
mkdir -p "$HOME/.local/bin"
|
|
131
|
-
mv rtk "$HOME/.local/bin/"
|
|
132
|
-
echo " Installed to: $HOME/.local/bin/rtk"
|
|
133
|
-
else
|
|
134
|
-
sudo mkdir -p /usr/local/bin
|
|
135
|
-
sudo mv rtk /usr/local/bin/
|
|
136
|
-
echo " Installed to: /usr/local/bin/rtk"
|
|
137
|
-
fi
|
|
138
|
-
|
|
139
|
-
INSTALL_SUCCESS=true
|
|
140
|
-
cd ..
|
|
141
|
-
rm -rf "$TEMP_DIR"
|
|
142
|
-
break
|
|
143
|
-
fi
|
|
144
|
-
fi
|
|
145
|
-
;;
|
|
146
|
-
esac
|
|
147
|
-
|
|
148
|
-
echo " ✗ Failed: $method"
|
|
149
|
-
done
|
|
150
|
-
|
|
151
|
-
# Verify installation
|
|
152
|
-
if [[ "$INSTALL_SUCCESS" == "true" ]]; then
|
|
153
|
-
if command -v rtk &> /dev/null; then
|
|
154
|
-
VERSION=$(rtk --version 2>&1 | head -1)
|
|
155
|
-
echo ""
|
|
156
|
-
echo "✅ RTK installed successfully!"
|
|
157
|
-
echo " Version: $VERSION"
|
|
158
|
-
echo ""
|
|
159
|
-
echo "📚 Next steps:"
|
|
160
|
-
echo " 1. Initialize hook for Claude Code:"
|
|
161
|
-
echo " rtk init --global"
|
|
162
|
-
echo ""
|
|
163
|
-
echo " 2. Verify installation:"
|
|
164
|
-
echo " rtk gain"
|
|
165
|
-
echo ""
|
|
166
|
-
echo " 3. View token savings:"
|
|
167
|
-
echo " rtk gain --graph"
|
|
168
|
-
echo ""
|
|
169
|
-
else
|
|
170
|
-
echo "❌ Installation completed but RTK binary not found in PATH"
|
|
171
|
-
echo " Please ensure $HOME/.local/bin is in your PATH:"
|
|
172
|
-
echo ' echo '\''export PATH="$HOME/.local/bin:$PATH"'\'' >> ~/.bashrc'
|
|
173
|
-
exit 1
|
|
174
|
-
fi
|
|
175
|
-
else
|
|
176
|
-
echo ""
|
|
177
|
-
echo "❌ All installation methods failed"
|
|
178
|
-
echo ""
|
|
179
|
-
echo "Manual installation options:"
|
|
180
|
-
echo " 1. Homebrew (macOS/Linux): brew install rtk"
|
|
181
|
-
echo " 2. Cargo: cargo install --git https://github.com/rtk-ai/rtk"
|
|
182
|
-
echo " 3. Pre-built binaries: https://github.com/rtk-ai/rtk/releases"
|
|
183
|
-
exit 1
|
|
184
|
-
fi
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""rtk-wrap enforcer: heavy CLIs must be invoked via rtk."""
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
import re
|
|
5
|
-
import sys
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
sys.path.insert(0, str(Path(__file__).parent))
|
|
9
|
-
from _common import emit, parse_cli # noqa: E402
|
|
10
|
-
|
|
11
|
-
WRAPPED = ("git", "kubectl", "docker", "docker-compose", "npm", "pnpm", "yarn", "helm", "terraform")
|
|
12
|
-
RTK_META = re.compile(r"^\s*rtk\s+(gain|discover|proxy|--version|-V|--help)\b")
|
|
13
|
-
ALREADY_WRAPPED = re.compile(r"^\s*rtk\s+\S+")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# `rtk npm`/`rtk pnpm`/`rtk yarn` map to `<pm> run` (filtered script output). The
|
|
17
|
-
# subcommands below are NOT script runners, so `rtk npm view` would mangle to
|
|
18
|
-
# `npm run view`. Route them through `rtk proxy` instead (still tracked).
|
|
19
|
-
PM_BUILTINS = {
|
|
20
|
-
"install", "i", "ci", "add", "view", "info", "show", "publish", "pack",
|
|
21
|
-
"audit", "outdated", "ls", "list", "link", "unlink", "dedupe", "prune",
|
|
22
|
-
"exec", "dlx", "init", "create", "ping", "whoami", "login", "logout",
|
|
23
|
-
"version", "deprecate", "dist-tag", "owner", "access", "search", "update",
|
|
24
|
-
"uninstall", "remove", "rm", "rebuild", "root", "bin", "config", "cache",
|
|
25
|
-
"doctor", "fund", "why", "store", "set", "get",
|
|
26
|
-
}
|
|
27
|
-
PMS = ("npm", "pnpm", "yarn")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# Flags that say "I am going to PARSE this". rtk rewrites output for human
|
|
31
|
-
# reading and does not notice these, so the caller gets prose where it asked
|
|
32
|
-
# for records. Verified against real git in this repo:
|
|
33
|
-
# worktree list --porcelain 46 entries -> 0 through rtk
|
|
34
|
-
# branch --format=... 55 refs -> 142 lines, wrong set
|
|
35
|
-
# diff --name-only 3 paths -> 3 paths + a "--- Changes ---" block
|
|
36
|
-
# status --porcelain 12 lines -> 11
|
|
37
|
-
# `rtk proxy` runs it unfiltered and is byte-exact on every one.
|
|
38
|
-
MACHINE_FLAGS = (
|
|
39
|
-
"--porcelain",
|
|
40
|
-
"--format",
|
|
41
|
-
"--pretty",
|
|
42
|
-
"--name-only",
|
|
43
|
-
"--name-status",
|
|
44
|
-
"--numstat",
|
|
45
|
-
"--raw",
|
|
46
|
-
"-z",
|
|
47
|
-
)
|
|
48
|
-
# NOT --oneline: it is a reading convenience, not a parse signal, and
|
|
49
|
-
# test/rtk-wrap.test.ts contracts it as accepted. Anything meaning to parse log
|
|
50
|
-
# output uses --format or --pretty, which are covered above. rtk does reformat
|
|
51
|
-
# `log --oneline`, so a caller that parses it gets altered text -- a narrow
|
|
52
|
-
# residual, accepted rather than taxing one of the most common human git calls.
|
|
53
|
-
|
|
54
|
-
# Subcommand forms rtk restructures even without one of those flags. Kept to
|
|
55
|
-
# what was actually measured rather than guessed, because every entry here is
|
|
56
|
-
# friction for a human-readable call that would have been fine.
|
|
57
|
-
MACHINE_FORMS = {
|
|
58
|
-
"branch": ("-r", "-a", "--merged", "--no-merged"),
|
|
59
|
-
"worktree": ("list",),
|
|
60
|
-
"stash": ("list",),
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
# rtk's docker handlers only recognise the BARE subcommand. Any flag on
|
|
64
|
-
# `docker ps` or `docker logs` makes rtk print "[rtk: parse failed, running
|
|
65
|
-
# raw]" ahead of otherwise-correct raw output. The DATA is fine -- but that
|
|
66
|
-
# line reads as a tool error, and a model that cannot act on it retries: it
|
|
67
|
-
# drove a live ERROR-LOOP on 2026-08-25 ("same failure x3,
|
|
68
|
-
# sig='[rtk: parse failed, running raw]'"). `rtk proxy` runs the same command
|
|
69
|
-
# unfiltered with no such line, and is still counted in the savings ledger.
|
|
70
|
-
#
|
|
71
|
-
# Measured against rtk 0.27.0 on 2026-08-25 -- listed rather than guessed,
|
|
72
|
-
# because every entry here is friction for a call that would have been fine:
|
|
73
|
-
# docker ps ok
|
|
74
|
-
# docker ps -a parse failed
|
|
75
|
-
# docker ps --filter name=uap parse failed
|
|
76
|
-
# docker ps --format '{{.Names}}' parse failed
|
|
77
|
-
# docker images ok
|
|
78
|
-
# docker logs --tail 1 <id> parse failed
|
|
79
|
-
# docker inspect <id> ok
|
|
80
|
-
# So the trigger is the SUBCOMMAND plus any flag at all, not a specific flag.
|
|
81
|
-
DOCKER_FLAG_INTOLERANT = ("ps", "logs")
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def docker_wants_proxy(tokens: list[str]) -> bool:
|
|
85
|
-
"""Is this a docker call rtk will fail to parse (bare form only)?"""
|
|
86
|
-
args = [t for t in tokens if t.split("/")[-1] != "docker"]
|
|
87
|
-
sub_cmd = next((t for t in args if not t.startswith("-")), "")
|
|
88
|
-
if sub_cmd not in DOCKER_FLAG_INTOLERANT:
|
|
89
|
-
return False
|
|
90
|
-
return any(t.startswith("-") for t in args)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def wants_machine_output(tokens: list[str]) -> bool:
|
|
94
|
-
"""Is this git invocation asking for output something will parse?"""
|
|
95
|
-
args = [t for t in tokens if t != "git"]
|
|
96
|
-
for tok in args:
|
|
97
|
-
if tok in MACHINE_FLAGS or tok.split("=")[0] in MACHINE_FLAGS:
|
|
98
|
-
return True
|
|
99
|
-
sub_cmd = next((t for t in args if not t.startswith("-")), "")
|
|
100
|
-
return any(t in MACHINE_FORMS.get(sub_cmd, ()) for t in args)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
# A command is a sequence of STATEMENTS; each one invokes its own binary.
|
|
104
|
-
_STATEMENT_SPLIT_RE = re.compile(r"&&|\|\||[;|&\n]")
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _leading_binary(statement: str) -> tuple[str, list[str]] | None:
|
|
108
|
-
"""The binary a statement invokes, plus its tokens — or None.
|
|
109
|
-
|
|
110
|
-
Skips env assignments (`FOO=bar cmd`) and shell grouping, then reads the
|
|
111
|
-
FIRST real word. That word is what the shell executes; a wrapped CLI
|
|
112
|
-
appearing later is an argument, not an invocation.
|
|
113
|
-
"""
|
|
114
|
-
tokens = statement.replace("(", " ").replace("{", " ").split()
|
|
115
|
-
idx = 0
|
|
116
|
-
while idx < len(tokens) and "=" in tokens[idx].split("/")[0]:
|
|
117
|
-
idx += 1 # env assignment prefix
|
|
118
|
-
if idx >= len(tokens):
|
|
119
|
-
return None
|
|
120
|
-
return tokens[idx].split("/")[-1], tokens[idx:]
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def main() -> None:
|
|
124
|
-
op, args = parse_cli()
|
|
125
|
-
cmd = (args.get("command") or args.get("cmd") or "").strip()
|
|
126
|
-
if not cmd or op.lower() != "bash":
|
|
127
|
-
emit(True, "not a Bash command")
|
|
128
|
-
|
|
129
|
-
# Evaluate each statement on its own.
|
|
130
|
-
#
|
|
131
|
-
# Whole-command matching got this wrong in BOTH directions. It only accepted
|
|
132
|
-
# `rtk` at the very start, so `cd /srv/app && rtk git log` — already wrapped —
|
|
133
|
-
# was refused, with a suggestion that prefixed rtk to the entire line and
|
|
134
|
-
# produced nonsense like `rtk cd /srv/app; git log`. And it scanned only the
|
|
135
|
-
# first three tokens, so `cd /srv/app && git log` slipped through entirely:
|
|
136
|
-
# `git` sat at index 3. That is a bypass, not just noise — any bare invocation
|
|
137
|
-
# could be hidden behind a `cd`. Reading the leading binary of each statement
|
|
138
|
-
# fixes both, because that is what the shell actually runs.
|
|
139
|
-
for raw in _STATEMENT_SPLIT_RE.split(cmd):
|
|
140
|
-
statement = raw.strip()
|
|
141
|
-
if not statement:
|
|
142
|
-
continue
|
|
143
|
-
found = _leading_binary(statement)
|
|
144
|
-
if not found:
|
|
145
|
-
continue
|
|
146
|
-
bin_name, tokens = found
|
|
147
|
-
|
|
148
|
-
# A statement led by `rtk` is normally fine: in `rtk git log` the
|
|
149
|
-
# leading binary is rtk and `git` is its argument. One exception --
|
|
150
|
-
# `rtk git <machine-readable>` is the corrupting combination, and
|
|
151
|
-
# skipping every rtk-led statement is what let it through the gate.
|
|
152
|
-
if bin_name == "rtk":
|
|
153
|
-
rest = [t for t in tokens[1:] if not t.startswith("-")]
|
|
154
|
-
if rest[:1] == ["docker"] and docker_wants_proxy(tokens[1:]):
|
|
155
|
-
emit(
|
|
156
|
-
False,
|
|
157
|
-
"rtk-wrap: rtk only parses the bare form of this docker "
|
|
158
|
-
"subcommand; with any flag it prefixes "
|
|
159
|
-
"'[rtk: parse failed, running raw]' to the output, which "
|
|
160
|
-
"reads as an error and has driven retry loops. Use: "
|
|
161
|
-
"rtk proxy " + " ".join(tokens[1:]),
|
|
162
|
-
bin="rtk",
|
|
163
|
-
)
|
|
164
|
-
if rest[:1] == ["git"] and wants_machine_output(tokens[1:]):
|
|
165
|
-
emit(
|
|
166
|
-
False,
|
|
167
|
-
"rtk-wrap: rtk reformats output for reading, so a git command "
|
|
168
|
-
"that asked for machine-readable output comes back as prose "
|
|
169
|
-
"(measured: `worktree list --porcelain` returns 0 entries "
|
|
170
|
-
"through rtk, 46 through git). Use: rtk proxy "
|
|
171
|
-
+ " ".join(tokens[1:]),
|
|
172
|
-
bin="rtk",
|
|
173
|
-
)
|
|
174
|
-
continue
|
|
175
|
-
|
|
176
|
-
if bin_name not in WRAPPED:
|
|
177
|
-
continue
|
|
178
|
-
|
|
179
|
-
wrapper = "rtk"
|
|
180
|
-
if bin_name == "git" and wants_machine_output(tokens):
|
|
181
|
-
# Still through rtk, so the call is still tracked -- just unfiltered.
|
|
182
|
-
wrapper = "rtk proxy"
|
|
183
|
-
elif bin_name == "docker" and docker_wants_proxy(tokens):
|
|
184
|
-
wrapper = "rtk proxy"
|
|
185
|
-
if bin_name in PMS:
|
|
186
|
-
sub = ""
|
|
187
|
-
for nxt in tokens[1:]:
|
|
188
|
-
if not nxt.startswith("-"):
|
|
189
|
-
sub = nxt.split("/")[-1]
|
|
190
|
-
break
|
|
191
|
-
if sub in PM_BUILTINS:
|
|
192
|
-
wrapper = "rtk proxy"
|
|
193
|
-
# Suggest fixing THIS statement, not the whole line.
|
|
194
|
-
emit(
|
|
195
|
-
False,
|
|
196
|
-
f"rtk-wrap: '{bin_name}' must be invoked via rtk. "
|
|
197
|
-
f"Use: {wrapper} {statement}",
|
|
198
|
-
bin=bin_name,
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
emit(True, "no unwrapped CLI in any statement")
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if __name__ == "__main__":
|
|
205
|
-
main()
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# rtk-wrap
|
|
2
|
-
|
|
3
|
-
**Category**: custom
|
|
4
|
-
**Level**: RECOMMENDED
|
|
5
|
-
**Default**: off
|
|
6
|
-
**Enforcement Stage**: pre-exec
|
|
7
|
-
**Tags**: rtk, tokens, efficiency
|
|
8
|
-
|
|
9
|
-
## Rule
|
|
10
|
-
|
|
11
|
-
Opt-in. Enable with `uap policy enable <id>`; disable with `uap policy disable <id>`.
|
|
12
|
-
|
|
13
|
-
When enabled, these commands SHOULD be invoked via the `rtk` wrapper rather than directly: `git`, `kubectl`, `docker`, `docker-compose`, `npm`, `pnpm`, `yarn`, `helm`, `terraform`.
|
|
14
|
-
|
|
15
|
-
Exceptions: `rtk` meta-commands (`rtk gain`, `rtk discover`, `rtk proxy`, `rtk --version`), and any invocation whose output will be PARSED — those must use `rtk proxy` (see below).
|
|
16
|
-
|
|
17
|
-
## Why
|
|
18
|
-
|
|
19
|
-
RTK delivers 60–90% token reduction on dev ops (`~/.claude/RTK.md`). Missing the wrap = proportional context waste.
|
|
20
|
-
|
|
21
|
-
## Why it is off by default
|
|
22
|
-
|
|
23
|
-
rtk rewrites command output for reading, and does not detect when the caller
|
|
24
|
-
asked for machine-readable output. Measured against real git in this repo:
|
|
25
|
-
|
|
26
|
-
| command | git | via rtk |
|
|
27
|
-
|---|---|---|
|
|
28
|
-
| `worktree list --porcelain` | 46 entries | **0 entries** |
|
|
29
|
-
| `branch --format='%(refname:short)'` | 55 refs | 142 lines, wrong set |
|
|
30
|
-
| `diff --name-only HEAD` | 3 paths | 3 paths + a `--- Changes ---` block |
|
|
31
|
-
| `status --porcelain` | 12 lines | 11 |
|
|
32
|
-
|
|
33
|
-
`rtk proxy git ...` is byte-exact on all of them.
|
|
34
|
-
|
|
35
|
-
While this policy was REQUIRED, that mangled output was what every agent read.
|
|
36
|
-
An agent parsing `^worktree ` out of `rtk git worktree list --porcelain` sees
|
|
37
|
-
nothing and concludes there are no worktrees — during a prune, the difference
|
|
38
|
-
between "skip" and "delete". The enforcer now routes parsed invocations to
|
|
39
|
-
`rtk proxy`, but the underlying trade is a judgement call, so it is offered
|
|
40
|
-
rather than imposed.
|
|
41
|
-
|
|
42
|
-
## Enforcement
|
|
43
|
-
|
|
44
|
-
Python enforcer `rtk_wrap.py` inspects the Bash command string and blocks if a wrapped binary is invoked without the `rtk ` prefix.
|
|
45
|
-
|
|
46
|
-
```rules
|
|
47
|
-
- title: "Wrap heavy CLIs with rtk"
|
|
48
|
-
keywords: [bash, shell, git, kubectl, docker, npm, pnpm, yarn, helm, terraform]
|
|
49
|
-
antiPatterns: [raw-kubectl, raw-git, raw-docker, raw-npm]
|
|
50
|
-
```
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
"""rtk-wrap: heavy CLIs go through rtk, but PARSED output must not.
|
|
2
|
-
|
|
3
|
-
rtk rewrites command output for token savings and does not notice when the
|
|
4
|
-
caller asked for machine-readable output. Measured against real git in this
|
|
5
|
-
repo:
|
|
6
|
-
|
|
7
|
-
git worktree list --porcelain 46 entries -> rtk: 0 entries
|
|
8
|
-
git branch --format='%(refname:short)' 55 refs -> rtk: 142 lines, wrong set
|
|
9
|
-
git diff --name-only HEAD 3 paths -> rtk: 3 paths + "--- Changes ---"
|
|
10
|
-
git status --porcelain 12 lines -> rtk: 11
|
|
11
|
-
git branch -r / --merged, stash list, log --oneline : all differ
|
|
12
|
-
|
|
13
|
-
`rtk proxy` runs the command unfiltered and was byte-exact on all eight.
|
|
14
|
-
|
|
15
|
-
This matters because the policy makes rtk mandatory, so the mangled output is
|
|
16
|
-
what every agent in this repo reads. Parsing `^worktree ` out of
|
|
17
|
-
`rtk git worktree list --porcelain` yields NOTHING, and an agent pruning
|
|
18
|
-
worktrees on that basis concludes there are none to protect.
|
|
19
|
-
|
|
20
|
-
The enforcer had no tests before this file, which is how `rtk git
|
|
21
|
-
<machine-readable>` -- the corrupting combination -- stayed invisible: every
|
|
22
|
-
statement led by `rtk` was skipped without looking at what followed.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import json
|
|
28
|
-
import subprocess
|
|
29
|
-
import sys
|
|
30
|
-
import unittest
|
|
31
|
-
from pathlib import Path
|
|
32
|
-
|
|
33
|
-
REPO = Path(__file__).resolve().parents[3]
|
|
34
|
-
ENFORCER = REPO / "src" / "policies" / "enforcers" / "rtk_wrap.py"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def gate(command: str) -> tuple[bool, str]:
|
|
38
|
-
r = subprocess.run(
|
|
39
|
-
[sys.executable, str(ENFORCER), "--operation", "Bash",
|
|
40
|
-
"--args", json.dumps({"command": command})],
|
|
41
|
-
capture_output=True, text=True, timeout=60,
|
|
42
|
-
)
|
|
43
|
-
try:
|
|
44
|
-
payload = json.loads(r.stdout or "{}")
|
|
45
|
-
except json.JSONDecodeError:
|
|
46
|
-
return True, f"unparseable: {r.stdout[:120]}"
|
|
47
|
-
return payload.get("allowed", True), payload.get("reason", "")
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class MachineReadableGitMustBypassTheFilter(unittest.TestCase):
|
|
51
|
-
"""The verdicts that stop an agent reading prose as records."""
|
|
52
|
-
|
|
53
|
-
MACHINE = [
|
|
54
|
-
"git worktree list --porcelain",
|
|
55
|
-
"git status --porcelain",
|
|
56
|
-
"git branch --format='%(refname:short)'",
|
|
57
|
-
"git diff --name-only HEAD",
|
|
58
|
-
"git log --pretty=%H",
|
|
59
|
-
# NOT `log --oneline`: a reading convenience, not a parse signal.
|
|
60
|
-
# test/rtk-wrap.test.ts contracts it as accepted, and anything that
|
|
61
|
-
# means to parse log output uses --format or --pretty.
|
|
62
|
-
"git diff --numstat HEAD",
|
|
63
|
-
"git ls-files -z",
|
|
64
|
-
"git branch -r",
|
|
65
|
-
"git branch --merged origin/master",
|
|
66
|
-
"git stash list",
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
def test_bare_git_asking_for_records_is_sent_to_rtk_proxy(self):
|
|
70
|
-
for cmd in self.MACHINE:
|
|
71
|
-
with self.subTest(cmd=cmd):
|
|
72
|
-
allowed, reason = gate(cmd)
|
|
73
|
-
self.assertFalse(allowed, cmd)
|
|
74
|
-
self.assertIn("rtk proxy", reason, f"{cmd}: must name the unfiltered form")
|
|
75
|
-
|
|
76
|
-
def test_rtk_git_asking_for_records_is_refused(self):
|
|
77
|
-
"""The combination that used to sail through.
|
|
78
|
-
|
|
79
|
-
Statements led by `rtk` were skipped without inspecting the rest, so
|
|
80
|
-
the one form that actually corrupts data was the one the gate ignored.
|
|
81
|
-
"""
|
|
82
|
-
for cmd in self.MACHINE:
|
|
83
|
-
with self.subTest(cmd=cmd):
|
|
84
|
-
allowed, reason = gate("rtk " + cmd)
|
|
85
|
-
self.assertFalse(allowed, f"rtk {cmd} returns mangled output")
|
|
86
|
-
self.assertIn("rtk proxy", reason)
|
|
87
|
-
|
|
88
|
-
def test_rtk_proxy_is_accepted(self):
|
|
89
|
-
for cmd in self.MACHINE:
|
|
90
|
-
with self.subTest(cmd=cmd):
|
|
91
|
-
allowed, _ = gate("rtk proxy " + cmd)
|
|
92
|
-
self.assertTrue(allowed, f"rtk proxy {cmd} is the correct form")
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class HumanReadableGitIsUnaffected(unittest.TestCase):
|
|
96
|
-
"""rtk's entire purpose is these, and the rule must not tax them."""
|
|
97
|
-
|
|
98
|
-
def test_ordinary_git_still_wants_plain_rtk(self):
|
|
99
|
-
for cmd in ("git status", "git log -5", "git diff", "git branch", "git show HEAD"):
|
|
100
|
-
with self.subTest(cmd=cmd):
|
|
101
|
-
allowed, reason = gate(cmd)
|
|
102
|
-
self.assertFalse(allowed, cmd)
|
|
103
|
-
self.assertIn("rtk", reason)
|
|
104
|
-
self.assertNotIn(
|
|
105
|
-
"rtk proxy", reason,
|
|
106
|
-
f"{cmd} is human-readable; sending it to proxy forfeits the token saving",
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
def test_already_wrapped_human_forms_pass(self):
|
|
110
|
-
for cmd in ("rtk git status", "rtk git log -5", "rtk git diff", "rtk git branch"):
|
|
111
|
-
with self.subTest(cmd=cmd):
|
|
112
|
-
allowed, _ = gate(cmd)
|
|
113
|
-
self.assertTrue(allowed, cmd)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
class ExistingBehaviourIsPreserved(unittest.TestCase):
|
|
117
|
-
"""The regressions this enforcer already carries scars from."""
|
|
118
|
-
|
|
119
|
-
def test_a_wrapped_cli_behind_a_cd_is_still_caught(self):
|
|
120
|
-
allowed, _ = gate("cd /srv/app && git log")
|
|
121
|
-
self.assertFalse(allowed, "a bare invocation hidden behind cd is a bypass")
|
|
122
|
-
|
|
123
|
-
def test_an_already_wrapped_statement_behind_a_cd_passes(self):
|
|
124
|
-
allowed, _ = gate("cd /srv/app && rtk git log")
|
|
125
|
-
self.assertTrue(allowed)
|
|
126
|
-
|
|
127
|
-
def test_npm_builtins_still_route_to_proxy(self):
|
|
128
|
-
allowed, reason = gate("npm view lodash")
|
|
129
|
-
self.assertFalse(allowed)
|
|
130
|
-
self.assertIn("rtk proxy", reason, "rtk npm view would mangle to `npm run view`")
|
|
131
|
-
|
|
132
|
-
def test_npm_scripts_still_route_to_plain_rtk(self):
|
|
133
|
-
allowed, reason = gate("npm test")
|
|
134
|
-
self.assertFalse(allowed)
|
|
135
|
-
self.assertNotIn("rtk proxy", reason)
|
|
136
|
-
|
|
137
|
-
def test_a_wrapped_cli_as_an_argument_is_not_an_invocation(self):
|
|
138
|
-
allowed, _ = gate("echo git status")
|
|
139
|
-
self.assertTrue(allowed, "git here is an argument to echo, not a command")
|
|
140
|
-
|
|
141
|
-
def test_non_bash_operations_are_ignored(self):
|
|
142
|
-
r = subprocess.run(
|
|
143
|
-
[sys.executable, str(ENFORCER), "--operation", "Edit",
|
|
144
|
-
"--args", json.dumps({"command": "git status --porcelain"})],
|
|
145
|
-
capture_output=True, text=True, timeout=60,
|
|
146
|
-
)
|
|
147
|
-
self.assertTrue(json.loads(r.stdout)["allowed"])
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if __name__ == "__main__":
|
|
151
|
-
unittest.main()
|