@windyroad/voice-tone 0.9.8 → 0.10.0

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.
@@ -145,5 +145,5 @@
145
145
  }
146
146
  },
147
147
  "name": "wr-voice-tone",
148
- "version": "0.9.8"
148
+ "version": "0.10.0"
149
149
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wr-voice-tone",
3
- "version": "0.9.8",
3
+ "version": "0.10.0",
4
4
  "description": "Voice, tone, and external-communications governance",
5
5
  "author": {
6
6
  "name": "Windy Road Technology",
@@ -439,11 +439,6 @@ print(json.dumps({
439
439
  " "$reason"
440
440
  }
441
441
 
442
- if [ "$BODY_FILE_ERROR" -eq 1 ]; then
443
- deny_with_reason "BLOCKED (external-comms gate): body-file could not be read unambiguously from the command checkout. Use one readable UTF-8 --body-file path and review its exact contents before retrying."
444
- exit 0
445
- fi
446
-
447
442
  permit_with_advisory() {
448
443
  local msg="$1"
449
444
  python3 -c "
@@ -452,6 +447,11 @@ print(json.dumps({'systemMessage': sys.argv[1]}))
452
447
  " "$msg"
453
448
  }
454
449
 
450
+ if [ "$BODY_FILE_ERROR" -eq 1 ]; then
451
+ permit_with_advisory "Governance evidence unavailable (external-comms gate): body-file could not be read unambiguously from the command checkout. Use one readable UTF-8 --body-file path and review its exact contents before retrying."
452
+ exit 0
453
+ fi
454
+
455
455
  # ---------- Advisory-only fallback when policy file is absent ----------
456
456
  if [ ! -f "$EXTERNAL_COMMS_POLICY_FILE" ]; then
457
457
  permit_with_advisory "$EXTERNAL_COMMS_POLICY_FILE not found — $EXTERNAL_COMMS_SUBAGENT_TYPE gate is advisory-only on $SURFACE."
@@ -537,5 +537,5 @@ VERDICT_PREFIX="${EXTERNAL_COMMS_VERDICT_PREFIX:-EXTERNAL_COMMS_${EXTERNAL_COMMS
537
537
  COMPLETION_GUIDANCE='On Codex, the calling agent waits for the reviewer to finish, then invokes `interrupt_agent` exactly once on that completed target before retrying; the PostToolUse compatibility hook consumes the completed response and persists its structured verdict, with no transcript parsing or nested codex exec. On Claude Code, the calling agent dispatches the reviewer synchronously (`run_in_background: false`) before retrying; the completion hook does not persist the marker for a background reviewer.'
538
538
  REASON=$(printf 'BLOCKED (external-comms gate / %s evaluator): %s draft has not been reviewed by %s. Delegate to %s (subagent_type: '"'"'%s'"'"') with a prompt that starts with the line `SURFACE: %s` and wraps the draft body verbatim inside `<draft>...</draft>` markers (for the changeset-author surface the body is the changeset summary WITHOUT the leading `---` frontmatter block — the gate strips frontmatter before hashing the marker key). The completion hook marks the draft reviewed when the subagent emits %s_VERDICT: PASS — single fire suffices. %s Use %s for an interactive walkthrough. There is no environment-variable override; `BYPASS_RISK_GATE` is unavailable.' \
539
539
  "$EXTERNAL_COMMS_EVALUATOR_ID" "$SURFACE" "$EXTERNAL_COMMS_SUBAGENT_TYPE" "$EXTERNAL_COMMS_SUBAGENT_TYPE" "$EXTERNAL_COMMS_SUBAGENT_TYPE" "$SURFACE" "$VERDICT_PREFIX" "$COMPLETION_GUIDANCE" "$EXTERNAL_COMMS_ASSESS_SKILL")
540
- deny_with_reason "$REASON"
540
+ permit_with_advisory "Governance evidence unavailable; action permitted. $REASON"
541
541
  exit 0
@@ -154,6 +154,9 @@ _HOOK_INPUT=""
154
154
 
155
155
  _parse_input() {
156
156
  _HOOK_INPUT=$(cat)
157
+ if ! printf '%s' "$_HOOK_INPUT" | python3 -c 'import json,sys; assert isinstance(json.load(sys.stdin),dict)' 2>/dev/null; then
158
+ _governance_advisory "Hook input could not be parsed."
159
+ fi
157
160
  }
158
161
 
159
162
  _get_tool_name() {
@@ -327,3 +330,8 @@ _is_doc_file() {
327
330
  esac
328
331
  return 1
329
332
  }
333
+
334
+ # Mechanical evaluation failures are advisory; never mint approval receipts.
335
+ _governance_advisory() {
336
+ python3 -c 'import json,sys; print(json.dumps({"systemMessage": "Governance evidence unavailable; action permitted. " + sys.argv[1]}))' "$1" || echo 'Governance evidence unavailable; action permitted.' >&2
337
+ }
@@ -7,10 +7,10 @@
7
7
  _REVIEW_GATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
8
  source "$_REVIEW_GATE_DIR/gate-helpers.sh"
9
9
 
10
- # Check review gate marker. Returns 0 if marker is valid (allow), 1 if invalid (deny).
10
+ # Validate review receipt; the public gate below converts unavailable evidence to an advisory.
11
11
  # Sets REVIEW_GATE_REASON on failure.
12
12
  # Usage: check_review_gate "$SESSION_ID" "style-guide" "docs/STYLE-GUIDE.md"
13
- check_review_gate() {
13
+ _check_review_receipt() {
14
14
  local SESSION_ID="$1"
15
15
  local SYSTEM="$2" # e.g., "a11y", "voice-tone", "style-guide"
16
16
  local POLICY_FILE="$3" # e.g., "docs/STYLE-GUIDE.md"
@@ -82,24 +82,21 @@ store_review_hash() {
82
82
  return 0
83
83
  }
84
84
 
85
- # Emit fail-closed deny JSON for PreToolUse hooks.
85
+ # Emit an advisory for unverifiable review requirements.
86
86
  review_gate_deny() {
87
- local REASON="$1"
88
- cat <<EOF
89
- {
90
- "hookSpecificOutput": {
91
- "hookEventName": "PreToolUse",
92
- "permissionDecision": "deny",
93
- "permissionDecisionReason": "$REASON"
94
- }
95
- }
96
- EOF
87
+ _governance_advisory "$1"
97
88
  }
98
89
 
99
- # Emit fail-closed deny JSON for parse failures.
90
+ # Permit parsing failures with an unavailable-evidence advisory.
100
91
  review_gate_parse_error() {
101
92
  cat <<'EOF'
102
- { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny",
103
- "permissionDecisionReason": "BLOCKED: Could not parse hook input. Gate is fail-closed." } }
93
+ { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow",
94
+ "permissionDecisionReason": "Governance evidence unavailable: could not parse hook input; action permitted." } }
104
95
  EOF
105
96
  }
97
+
98
+ check_review_gate() {
99
+ if _check_review_receipt "$@"; then return 0; fi
100
+ _governance_advisory "$REVIEW_GATE_REASON"
101
+ return 0
102
+ }
@@ -5,8 +5,34 @@ target="$1"
5
5
  input="$(cat)"
6
6
  tool="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
7
7
 
8
+ ADVISORIES=""
9
+ collect_gate_output() {
10
+ local output="$1" parsed
11
+ [ -n "$output" ] || return 0
12
+ if ! parsed="$(printf '%s' "$output" | jq -cs '
13
+ if any(.[]; .hookSpecificOutput.permissionDecision == "deny")
14
+ then map(select(.hookSpecificOutput.permissionDecision == "deny"))[0]
15
+ else {systemMessage: map(.systemMessage // .hookSpecificOutput.permissionDecisionReason // "Governance evidence unavailable; action permitted.") | join("\n")} end' 2>/dev/null)"; then
16
+ parsed='{"systemMessage":"Governance evidence unavailable: malformed evaluator output; action permitted."}'
17
+ fi
18
+ if printf '%s' "$parsed" | jq -e '.hookSpecificOutput.permissionDecision == "deny"' >/dev/null 2>&1; then
19
+ printf '%s\n' "$parsed"
20
+ exit 0
21
+ fi
22
+ ADVISORIES="$ADVISORIES
23
+ $parsed"
24
+ }
25
+ flush_advisories() {
26
+ [ -n "$ADVISORIES" ] || return 0
27
+ printf '%s' "$ADVISORIES" | jq -s '{systemMessage: map(.systemMessage // .hookSpecificOutput.permissionDecisionReason // "Governance evidence unavailable; action permitted.") | join("\n")}'
28
+ }
8
29
  run_hook() {
9
- printf '%s' "$1" | "$target"
30
+ local output
31
+ if ! output="$(printf '%s' "$1" | "$target" 2>/dev/null)"; then
32
+ collect_gate_output '{"systemMessage":"Governance evaluator unavailable; action permitted. A crashed hook output was discarded."}'
33
+ return 0
34
+ fi
35
+ collect_gate_output "$output"
10
36
  }
11
37
 
12
38
  if [ "$tool" = "apply_patch" ]; then
@@ -16,6 +42,7 @@ if [ "$tool" = "apply_patch" ]; then
16
42
  payload="$(printf '%s' "$input" | jq --arg path "$path" '.tool_name = "Edit" | .tool_input.file_path = $path')"
17
43
  run_hook "$payload" || exit $?
18
44
  done <<< "$paths"
45
+ flush_advisories
19
46
  exit 0
20
47
  fi
21
48
  fi
@@ -25,3 +52,4 @@ if [ "$tool" = "spawn_agent" ] || [ "$tool" = "Agent" ]; then
25
52
  fi
26
53
 
27
54
  run_hook "$input"
55
+ flush_advisories
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/voice-tone",
3
- "version": "0.9.8",
3
+ "version": "0.10.0",
4
4
  "description": "Voice and tone enforcement for user-facing copy",
5
5
  "bin": {
6
6
  "windyroad-voice-tone": "./bin/install.mjs"