@windyroad/voice-tone 0.5.12 → 0.5.14-preview.746

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.
@@ -123,5 +123,5 @@
123
123
  }
124
124
  },
125
125
  "name": "wr-voice-tone",
126
- "version": "0.5.12"
126
+ "version": "0.5.14"
127
127
  }
@@ -23,3 +23,11 @@ EXTERNAL_COMMS_POLICY_FILE=docs/VOICE-AND-TONE.md
23
23
  # concern (run by packages/risk-scorer/hooks/external-comms-gate.sh when that
24
24
  # plugin is also installed).
25
25
  EXTERNAL_COMMS_LEAK_PREFILTER=no
26
+
27
+ # Surfaces this evaluator's policy doc disclaims — the gate silent-passes the
28
+ # marker-review delegation on them (P360). docs/VOICE-AND-TONE.md § Scope
29
+ # excludes commit messages ("It does NOT apply to: ... Commit messages (covered
30
+ # by ADR-014 + ADR-018)"), so a voice-tone review of the git-commit-message
31
+ # surface is a guaranteed-PASS no-op (~19K tokens). Skip it. The risk evaluator
32
+ # does NOT skip this surface — its leak check on commit bodies is meaningful.
33
+ EXTERNAL_COMMS_SKIP_SURFACES=git-commit-message
@@ -78,6 +78,10 @@ source "$SCRIPT_DIR/lib/external-comms-key.sh"
78
78
  # EXTERNAL_COMMS_ASSESS_SKILL — on-demand skill path for manual delegation
79
79
  # EXTERNAL_COMMS_POLICY_FILE — policy doc whose absence triggers advisory-only
80
80
  # EXTERNAL_COMMS_LEAK_PREFILTER — yes|no — whether to run leak-detect pre-filter
81
+ # EXTERNAL_COMMS_SKIP_SURFACES — space-separated surface list this evaluator's
82
+ # policy disclaims; the marker-review delegation
83
+ # silent-passes on those surfaces (P360). Default
84
+ # empty (gate every detected surface).
81
85
  # Fail-closed if absent: this hook cannot operate without a configured evaluator.
82
86
  CONF_FILE="$SCRIPT_DIR/external-comms-evaluator.conf"
83
87
  if [ ! -f "$CONF_FILE" ]; then
@@ -91,6 +95,7 @@ source "$CONF_FILE"
91
95
  : "${EXTERNAL_COMMS_ASSESS_SKILL:?assess-skill missing from $CONF_FILE}"
92
96
  EXTERNAL_COMMS_POLICY_FILE="${EXTERNAL_COMMS_POLICY_FILE:-RISK-POLICY.md}"
93
97
  EXTERNAL_COMMS_LEAK_PREFILTER="${EXTERNAL_COMMS_LEAK_PREFILTER:-yes}"
98
+ EXTERNAL_COMMS_SKIP_SURFACES="${EXTERNAL_COMMS_SKIP_SURFACES:-}"
94
99
 
95
100
  # ---------- Bypass ----------
96
101
  if [ "${BYPASS_RISK_GATE:-0}" = "1" ]; then
@@ -318,6 +323,48 @@ if [ "$EXTERNAL_COMMS_LEAK_PREFILTER" = "yes" ]; then
318
323
  fi
319
324
  fi
320
325
 
326
+ # ---------- Per-evaluator surface skip (P360) ----------
327
+ # Some surfaces are explicitly disclaimed by THIS evaluator's policy doc, so the
328
+ # marker-review delegation below would be a guaranteed-PASS no-op (the subagent
329
+ # reads the policy, declares the surface out of scope, emits PASS — ~19K tokens
330
+ # per round-trip). EXTERNAL_COMMS_SKIP_SURFACES (per-package .conf) lists those
331
+ # surfaces; the gate silent-passes the prose-review delegation when the detected
332
+ # surface is on the list. Voice-tone sets this to `git-commit-message` because
333
+ # docs/VOICE-AND-TONE.md § Scope excludes commit messages ("covered by ADR-014 +
334
+ # ADR-018"); risk-scorer leaves it empty (its leak check on commit messages is
335
+ # meaningful). Placed AFTER the leak pre-filter so a skipped surface still gets
336
+ # credential/prod-URL scanning — this silences ONLY the prose-review deny, the
337
+ # same conservative shape as the P365 repo-visibility precondition below.
338
+ if [ -n "$EXTERNAL_COMMS_SKIP_SURFACES" ]; then
339
+ case " $EXTERNAL_COMMS_SKIP_SURFACES " in
340
+ *" $SURFACE "*)
341
+ exit 0
342
+ ;;
343
+ esac
344
+ fi
345
+
346
+ # ---------- Repo-visibility precondition: git-commit-message surface (P365) ----------
347
+ # A commit message only becomes external-facing prose when it lands in a PUBLIC
348
+ # GitHub repo (git log / PR commits tab / release-page auto-notes / CHANGELOG).
349
+ # In private or internal repos the marker-review delegation deny below is a pure
350
+ # false-positive (P365 — user direction 2026-06-11: "this MUST NOT fire for
351
+ # private repos"). Confirm visibility authoritatively via gh and silent-pass the
352
+ # marker gate on any non-PUBLIC result. Any INDETERMINATE result (gh absent,
353
+ # unauthenticated, no remote, API error → empty $REPO_VISIBILITY) is treated as
354
+ # non-public: a commit message is only demonstrably external when the repo is
355
+ # confirmably PUBLIC, so the conservative direction for THIS surface is to not
356
+ # fire. This is a fail-open on the voice/tone-and-prose review ONLY — the
357
+ # leak-pattern pre-filter above (credentials / prod-URLs) has already run for
358
+ # every surface in every repo, so the high-stakes secrecy net is unaffected.
359
+ # Scoped to git-commit-message only; the gh-issue/pr/api, npm-publish, and
360
+ # changeset-author surfaces are inherently external and stay gated regardless.
361
+ if [ "$SURFACE" = "git-commit-message" ]; then
362
+ REPO_VISIBILITY=$(gh repo view --json visibility -q .visibility 2>/dev/null || echo "")
363
+ if [ "$REPO_VISIBILITY" != "PUBLIC" ]; then
364
+ exit 0
365
+ fi
366
+ fi
367
+
321
368
  # ---------- Marker-based gate (per-evaluator marker per ADR-028 amended 2026-05-14) ----------
322
369
  SESSION_DIR="${TMPDIR:-/tmp}/claude-risk-${SESSION_ID}"
323
370
  mkdir -p "$SESSION_DIR"
@@ -61,9 +61,23 @@ print(json.dumps({
61
61
  " "$file_path" "$content"
62
62
  }
63
63
 
64
+ # Mock `gh repo view --json visibility` for the git-commit-message surface
65
+ # repo-visibility precondition (P365). vis ∈ {PUBLIC,PRIVATE,INTERNAL}; pass the
66
+ # literal "FAIL" to simulate gh absent / unauthenticated (non-zero exit).
67
+ mock_gh_visibility() {
68
+ local vis="$1"
69
+ mkdir -p "$TEST_PROJECT_DIR/mockbin"
70
+ if [ "$vis" = "FAIL" ]; then
71
+ printf '#!/usr/bin/env bash\nexit 1\n' > "$TEST_PROJECT_DIR/mockbin/gh"
72
+ else
73
+ printf '#!/usr/bin/env bash\necho %s\n' "$vis" > "$TEST_PROJECT_DIR/mockbin/gh"
74
+ fi
75
+ chmod +x "$TEST_PROJECT_DIR/mockbin/gh"
76
+ }
77
+
64
78
  run_hook() {
65
79
  local input="$1"
66
- run bash -c "cd '$TEST_PROJECT_DIR' && printf '%s' \"\$1\" | '$HOOK'" _ "$input"
80
+ run bash -c "cd '$TEST_PROJECT_DIR' && export PATH='$TEST_PROJECT_DIR/mockbin':\$PATH && printf '%s' \"\$1\" | '$HOOK'" _ "$input"
67
81
  }
68
82
 
69
83
  # ---------- Tests ----------
@@ -233,56 +247,75 @@ run_hook() {
233
247
  }
234
248
 
235
249
  # ---------------------------------------------------------------------------
236
- # P082 Phase 1 git commit message surface (voice-tone evaluator).
237
- # Commit messages reach git log / PR commits tab / release notes / CHANGELOG;
238
- # voice-tone evaluator gates the message body for AI-tells, hedging,
239
- # em-dashes, banned-phrase drift before the commit lands. Editor flow
240
- # (bare `git commit`) is out of scope per P082 SC1 — the message is
241
- # written to .git/COMMIT_EDITMSG AFTER PreToolUse fires.
250
+ # P360 voice-tone evaluator disclaims the git-commit-message surface.
251
+ # docs/VOICE-AND-TONE.md § Scope explicitly excludes commit messages ("It does
252
+ # NOT apply to: ... Commit messages (covered by ADR-014 + ADR-018)"), so a
253
+ # voice-tone review of a commit-message body is a guaranteed-PASS no-op
254
+ # (~19K tokens per round-trip). external-comms-evaluator.conf sets
255
+ # EXTERNAL_COMMS_SKIP_SURFACES=git-commit-message, so the gate silent-passes the
256
+ # prose-review delegation on this surface in EVERY repo — visibility-independent
257
+ # (the P360 skip is placed ahead of the P365 visibility precondition). The
258
+ # risk-scorer evaluator, whose .conf leaves the skip list empty, still gates
259
+ # this surface (its leak check is meaningful) — covered by
260
+ # packages/risk-scorer/hooks/test/external-comms-gate.bats.
261
+ #
262
+ # Supersedes the original P082 Phase 1 voice-tone commit-message tests, which
263
+ # asserted DENY on PUBLIC — that gate fire was the no-op P360 removes.
242
264
  # ---------------------------------------------------------------------------
243
265
 
244
- @test "P082: git commit -m with literal -m body denies and delegates to voice-tone evaluator" {
266
+ @test "P360: git commit -m silent-passes (voice-tone disclaims commit messages)" {
267
+ mock_gh_visibility PUBLIC
245
268
  INPUT=$(build_bash_input "git commit -m \"I've implemented the feature\"")
246
269
  run_hook "$INPUT"
247
270
  [ "$status" -eq 0 ]
248
- [[ "$output" == *"deny"* ]]
249
- [[ "$output" == *"git-commit-message"* ]]
250
- [[ "$output" == *"wr-voice-tone:external-comms"* ]]
271
+ [ -z "$output" ]
251
272
  }
252
273
 
253
- @test "P082: git commit --message with literal body denies and delegates" {
274
+ @test "P360: git commit --message silent-passes with no marker round-trip" {
275
+ mock_gh_visibility PUBLIC
254
276
  INPUT=$(build_bash_input "git commit --message \"happy to help further with this fix\"")
255
277
  run_hook "$INPUT"
256
278
  [ "$status" -eq 0 ]
257
- [[ "$output" == *"deny"* ]]
258
- [[ "$output" == *"git-commit-message"* ]]
279
+ [ -z "$output" ]
259
280
  }
260
281
 
261
- @test "P082: git commit --amend -m is intercepted (P082 SC2)" {
282
+ @test "P360: git commit --amend -m silent-passes" {
283
+ mock_gh_visibility PUBLIC
262
284
  INPUT=$(build_bash_input "git commit --amend -m \"rewritten subject\"")
263
285
  run_hook "$INPUT"
264
286
  [ "$status" -eq 0 ]
265
- [[ "$output" == *"deny"* ]]
266
- [[ "$output" == *"git-commit-message"* ]]
287
+ [ -z "$output" ]
267
288
  }
268
289
 
269
- @test "P082: git commit HEREDOC body is intercepted and the body becomes the marker key" {
270
- # Build a HEREDOC-shaped command. The hook regex pulls the body BETWEEN
271
- # the <<'EOF' opener and the closing EOF marker — the extracted DRAFT is
272
- # the inner text, NOT the literal `$(cat <<'EOF' ... EOF)` wrapper.
290
+ @test "P360: git commit HEREDOC body silent-passes without a marker" {
291
+ mock_gh_visibility PUBLIC
273
292
  BODY=$'feat(foo): add bar\n\nWe observed a build failure on Node 20.'
274
293
  CMD=$'git commit -m "$(cat <<\'EOF\'\n'"$BODY"$'\nEOF\n)"'
275
294
  INPUT=$(build_bash_input "$CMD")
276
295
  run_hook "$INPUT"
277
296
  [ "$status" -eq 0 ]
278
- [[ "$output" == *"deny"* ]]
279
- [[ "$output" == *"git-commit-message"* ]]
297
+ [ -z "$output" ]
298
+ }
280
299
 
281
- # Pre-place the per-evaluator marker keyed on the extracted HEREDOC body
282
- # + the git-commit-message surface; the second run must permit silently.
283
- SURFACE="git-commit-message"
284
- KEY=$(printf '%s\n%s' "$BODY" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
285
- touch "${RDIR}/external-comms-voice-tone-reviewed-${KEY}"
300
+ @test "P360: commit-message skip is visibility-independent (PRIVATE silent-passes)" {
301
+ mock_gh_visibility PRIVATE
302
+ INPUT=$(build_bash_input "git commit -m \"I've implemented the feature\"")
303
+ run_hook "$INPUT"
304
+ [ "$status" -eq 0 ]
305
+ [ -z "$output" ]
306
+ }
307
+
308
+ @test "P360: commit-message skip is visibility-independent (INTERNAL silent-passes)" {
309
+ mock_gh_visibility INTERNAL
310
+ INPUT=$(build_bash_input "git commit -m \"I've implemented the feature\"")
311
+ run_hook "$INPUT"
312
+ [ "$status" -eq 0 ]
313
+ [ -z "$output" ]
314
+ }
315
+
316
+ @test "P360: commit-message skip is visibility-independent (indeterminate gh silent-passes)" {
317
+ mock_gh_visibility FAIL
318
+ INPUT=$(build_bash_input "git commit -m \"I've implemented the feature\"")
286
319
  run_hook "$INPUT"
287
320
  [ "$status" -eq 0 ]
288
321
  [ -z "$output" ]
@@ -311,16 +344,25 @@ run_hook() {
311
344
  [ -z "$output" ]
312
345
  }
313
346
 
314
- @test "P082: per-evaluator marker keyed on (body, git-commit-message) permits the call" {
315
- BODY="docs(retro): close iter 3 ask-hygiene trail"
316
- SURFACE="git-commit-message"
317
- KEY=$(printf '%s\n%s' "$BODY" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
318
- touch "${RDIR}/external-comms-voice-tone-reviewed-${KEY}"
347
+ @test "P360: the skip is surface-scoped gh-issue is NOT skipped (still denies+delegates)" {
348
+ # EXTERNAL_COMMS_SKIP_SURFACES lists only git-commit-message; the inherently
349
+ # external gh-issue surface stays gated. Guards against the skip list over-
350
+ # matching (e.g. a substring or blanket-skip regression).
351
+ mock_gh_visibility PUBLIC
352
+ INPUT=$(build_bash_input "gh issue create --title x --body 'a clean issue body'")
353
+ run_hook "$INPUT"
354
+ [ "$status" -eq 0 ]
355
+ [[ "$output" == *"deny"* ]]
356
+ [[ "$output" == *"wr-voice-tone:external-comms"* ]]
357
+ }
319
358
 
320
- INPUT=$(build_bash_input "git commit -m \"$BODY\"")
359
+ @test "P365: PRIVATE visibility does NOT short-circuit the gh-issue surface (still denies+delegates)" {
360
+ mock_gh_visibility PRIVATE
361
+ INPUT=$(build_bash_input "gh issue create --title x --body 'a clean issue body'")
321
362
  run_hook "$INPUT"
322
363
  [ "$status" -eq 0 ]
323
- [ -z "$output" ]
364
+ [[ "$output" == *"deny"* ]]
365
+ [[ "$output" == *"gh-issue-create"* ]]
324
366
  }
325
367
 
326
368
  # ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/voice-tone",
3
- "version": "0.5.12",
3
+ "version": "0.5.14-preview.746",
4
4
  "description": "Voice and tone enforcement for user-facing copy",
5
5
  "bin": {
6
6
  "windyroad-voice-tone": "./bin/install.mjs"