@rubytech/create-maxy-code 0.1.569 → 0.1.570

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.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +3 -3
  3. package/payload/platform/plugins/admin/hooks/__tests__/pdf-text-layer-inject.test.sh +177 -0
  4. package/payload/platform/plugins/admin/hooks/pdf-text-layer-inject.sh +137 -0
  5. package/payload/platform/plugins/admin/skills/a4-print-documents/SKILL.md +12 -1
  6. package/payload/platform/plugins/admin/skills/a4-print-documents/pdf-inspect.mjs +113 -0
  7. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  8. package/payload/platform/plugins/docs/references/admin-session.md +15 -0
  9. package/payload/platform/scripts/__tests__/mailbox-inject-registered.test.sh +15 -39
  10. package/payload/platform/scripts/__tests__/public-prompt-gate-registered.test.sh +22 -37
  11. package/payload/platform/scripts/__tests__/public-surface-registered.test.sh +8 -31
  12. package/payload/platform/scripts/setup-account.sh +7 -62
  13. package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.d.ts +129 -0
  14. package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.d.ts.map +1 -0
  15. package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.js +344 -0
  16. package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.js.map +1 -0
  17. package/payload/platform/services/claude-session-manager/dist/index.js +20 -0
  18. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  19. package/payload/platform/templates/account-settings.json +6 -0
  20. package/payload/server/server.js +750 -656
  21. package/payload/platform/scripts/lib/__tests__/account-settings-askgate.test.sh +0 -144
  22. package/payload/platform/scripts/lib/__tests__/account-settings-mailbox-inject.test.sh +0 -166
  23. package/payload/platform/scripts/lib/__tests__/account-settings-pdf-conformance.test.sh +0 -151
  24. package/payload/platform/scripts/lib/__tests__/account-settings-public-surface.test.sh +0 -109
  25. package/payload/platform/scripts/lib/account-settings-askgate.sh +0 -98
  26. package/payload/platform/scripts/lib/account-settings-mailbox-inject.sh +0 -91
  27. package/payload/platform/scripts/lib/account-settings-pdf-conformance.sh +0 -98
  28. package/payload/platform/scripts/lib/account-settings-public-surface.sh +0 -109
@@ -1,144 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Regression test for account-settings-askgate.sh (Task 1683).
3
- #
4
- # The lib retrofits the AskUserQuestion carrier gate onto existing accounts'
5
- # .claude/settings.json on every install/upgrade. Task 1552 added the gate to
6
- # provision-account-dir.sh, but that writer only runs at account-provision time
7
- # and the upgrade path never re-provisions existing client sub-accounts — so an
8
- # account provisioned before 1552 keeps a stale AskUserQuestion matcher with no
9
- # carrier gate and re-wedges on a channel AskUserQuestion (live SiteDesk
10
- # incident, session d39a22b0, account 2078cb54).
11
- #
12
- # Covers:
13
- # 1. Stale matcher (investigate only) -> updated, carrier appended, idempotent on re-run
14
- # 2. Correct matcher (both gates) -> already-set, file byte-identical
15
- # 3. AskUserQuestion matcher absent -> updated, matcher added with both gates
16
- # 4. No .hooks.PreToolUse -> no-hooks, file untouched
17
- # 5. File absent -> absent
18
- # 6. Malformed JSON -> rewrite-failed, file untouched
19
- # 7. reconcile_all_accounts_askgate -> patches every account.json dir, skips non-account dirs
20
- #
21
- # All cases assert the [backfill-1683] status token and the post-state of the file.
22
-
23
- set -u
24
-
25
- LIB="$(cd "$(dirname "$0")/.." && pwd)/account-settings-askgate.sh"
26
- if [ ! -f "$LIB" ]; then
27
- echo "FAIL: $LIB not found" >&2
28
- exit 1
29
- fi
30
- # shellcheck source=/dev/null
31
- . "$LIB"
32
-
33
- CARRIER='bash $PLATFORM_ROOT/plugins/admin/hooks/askuserquestion-channel-carrier-gate.sh'
34
- INVEST='bash $PLATFORM_ROOT/plugins/admin/hooks/askuserquestion-investigate-gate.sh'
35
-
36
- PASS=0
37
- FAIL=0
38
- TMP="$(mktemp -d)"
39
- trap 'rm -rf "$TMP"' EXIT
40
-
41
- ok() { PASS=$((PASS+1)); }
42
- bad() { FAIL=$((FAIL+1)); echo "FAIL: $1" >&2; }
43
-
44
- # settings.json with an AskUserQuestion matcher carrying the given command list.
45
- # Args after $1 are command strings for the AskUserQuestion matcher.
46
- write_settings() {
47
- local file="$1"; shift
48
- local cmds="" c
49
- for c in "$@"; do
50
- [ -n "$cmds" ] && cmds="$cmds,"
51
- cmds="$cmds{\"type\":\"command\",\"command\":\"$c\"}"
52
- done
53
- mkdir -p "$(dirname "$file")"
54
- cat > "$file" <<EOF
55
- {
56
- "permissions": { "defaultMode": "bypassPermissions", "allow": [], "deny": [] },
57
- "hooks": {
58
- "PreToolUse": [
59
- { "matcher": "Write", "hooks": [ { "type": "command", "command": "bash \$P/w.sh" } ] },
60
- { "matcher": "AskUserQuestion", "hooks": [ $cmds ] }
61
- ]
62
- }
63
- }
64
- EOF
65
- }
66
-
67
- carrier_count() { grep -cF 'askuserquestion-channel-carrier-gate.sh' "$1" 2>/dev/null || true; }
68
- askmatcher_present() {
69
- jq -e '.hooks.PreToolUse | any(.[]; .matcher == "AskUserQuestion")' "$1" >/dev/null 2>&1
70
- }
71
-
72
- # --- 1. stale matcher -> updated, then idempotent ---------------------------
73
- F="$TMP/stale/.claude/settings.json"
74
- write_settings "$F" "$INVEST"
75
- OUT="$(ensure_askgate_carrier "$F")"
76
- case "$OUT" in *status=updated*) ok;; *) bad "1a stale should be updated, got: $OUT";; esac
77
- [ "$(carrier_count "$F")" -eq 1 ] && ok || bad "1b carrier not appended (count=$(carrier_count "$F"))"
78
- # investigate still present and first
79
- if jq -e '.hooks.PreToolUse[] | select(.matcher=="AskUserQuestion") | .hooks[0].command | test("investigate-gate")' "$F" >/dev/null 2>&1; then ok; else bad "1c investigate no longer first"; fi
80
- OUT2="$(ensure_askgate_carrier "$F")"
81
- case "$OUT2" in *status=already-set*) ok;; *) bad "1d re-run should be already-set, got: $OUT2";; esac
82
- [ "$(carrier_count "$F")" -eq 1 ] && ok || bad "1e carrier duplicated on re-run (count=$(carrier_count "$F"))"
83
-
84
- # --- 2. correct matcher -> already-set, byte-identical ----------------------
85
- F="$TMP/good/.claude/settings.json"
86
- write_settings "$F" "$INVEST" "$CARRIER"
87
- BEFORE="$(cat "$F")"
88
- OUT="$(ensure_askgate_carrier "$F")"
89
- case "$OUT" in *status=already-set*) ok;; *) bad "2a good should be already-set, got: $OUT";; esac
90
- [ "$BEFORE" = "$(cat "$F")" ] && ok || bad "2b already-set file was rewritten"
91
-
92
- # --- 3. AskUserQuestion matcher absent -> updated, matcher added -------------
93
- F="$TMP/nomatcher/.claude/settings.json"
94
- mkdir -p "$(dirname "$F")"
95
- cat > "$F" <<'EOF'
96
- { "permissions": {}, "hooks": { "PreToolUse": [ { "matcher": "Write", "hooks": [] } ] } }
97
- EOF
98
- OUT="$(ensure_askgate_carrier "$F")"
99
- case "$OUT" in *status=updated*) ok;; *) bad "3a nomatcher should be updated, got: $OUT";; esac
100
- askmatcher_present "$F" && ok || bad "3b AskUserQuestion matcher not added"
101
- [ "$(carrier_count "$F")" -eq 1 ] && ok || bad "3c carrier missing after add"
102
- if jq -e '.hooks.PreToolUse[] | select(.matcher=="AskUserQuestion") | .hooks | length == 2' "$F" >/dev/null 2>&1; then ok; else bad "3d added matcher should carry both gates"; fi
103
-
104
- # --- 4. no PreToolUse -> no-hooks, untouched --------------------------------
105
- F="$TMP/nohooks/.claude/settings.json"
106
- mkdir -p "$(dirname "$F")"
107
- echo '{ "permissions": {}, "hooks": {} }' > "$F"
108
- BEFORE="$(cat "$F")"
109
- OUT="$(ensure_askgate_carrier "$F")"
110
- case "$OUT" in *status=no-hooks*) ok;; *) bad "4a should be no-hooks, got: $OUT";; esac
111
- [ "$BEFORE" = "$(cat "$F")" ] && ok || bad "4b no-hooks file was rewritten"
112
-
113
- # --- 5. file absent -> absent ------------------------------------------------
114
- OUT="$(ensure_askgate_carrier "$TMP/does-not-exist/.claude/settings.json")"
115
- case "$OUT" in *status=absent*) ok;; *) bad "5 should be absent, got: $OUT";; esac
116
-
117
- # --- 6. malformed JSON -> rewrite-failed, untouched -------------------------
118
- F="$TMP/bad/.claude/settings.json"
119
- mkdir -p "$(dirname "$F")"
120
- printf '{ this is not json ' > "$F"
121
- BEFORE="$(cat "$F")"
122
- OUT="$(ensure_askgate_carrier "$F")"
123
- case "$OUT" in *status=rewrite-failed*) ok;; *) bad "6a malformed should be rewrite-failed, got: $OUT";; esac
124
- [ "$BEFORE" = "$(cat "$F")" ] && ok || bad "6b malformed file was mutated"
125
- [ ! -e "$F.1683.tmp" ] && ok || bad "6c temp file left behind"
126
-
127
- # --- 7. reconcile_all_accounts_askgate --------------------------------------
128
- ROOT="$TMP/accts"
129
- mkdir -p "$ROOT/acctA/.claude" "$ROOT/acctB/.claude" "$ROOT/notanaccount/.claude"
130
- echo '{"role":"house"}' > "$ROOT/acctA/account.json"
131
- echo '{"role":"client"}' > "$ROOT/acctB/account.json"
132
- # notanaccount has no account.json -> must be skipped
133
- write_settings "$ROOT/acctA/.claude/settings.json" "$INVEST"
134
- write_settings "$ROOT/acctB/.claude/settings.json" "$INVEST" "$CARRIER"
135
- write_settings "$ROOT/notanaccount/.claude/settings.json" "$INVEST"
136
- RECOUT="$(reconcile_all_accounts_askgate "$ROOT")"
137
- [ "$(carrier_count "$ROOT/acctA/.claude/settings.json")" -eq 1 ] && ok || bad "7a acctA not patched"
138
- [ "$(carrier_count "$ROOT/acctB/.claude/settings.json")" -eq 1 ] && ok || bad "7b acctB disturbed"
139
- [ "$(carrier_count "$ROOT/notanaccount/.claude/settings.json")" -eq 0 ] && ok || bad "7c non-account dir was patched"
140
- case "$RECOUT" in *acctA*status=updated*) ok;; *) bad "7d reconcile did not log acctA updated";; esac
141
-
142
- echo "----------------------------------------"
143
- echo "PASS=$PASS FAIL=$FAIL"
144
- [ "$FAIL" -eq 0 ]
@@ -1,166 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Regression test for account-settings-mailbox-inject.sh (Task 2157).
3
- #
4
- # The lib retrofits the per-turn <mailboxes> injection hook onto existing
5
- # accounts' .claude/settings.json on every install. provision-account-dir.sh
6
- # writes the UserPromptSubmit array at provision time only, and setup-account.sh
7
- # re-provisions the HOUSE account only, so a client sub-account created by the
8
- # admin-core account-lifecycle tool never gets the hook without this reconcile.
9
- #
10
- # Covers:
11
- # 1. Array with a sibling hook -> updated, command appended, sibling preserved
12
- # 2. Re-run on the same file -> already-set, file byte-identical
13
- # 3. Empty UserPromptSubmit array-> updated, whole entry created
14
- # 4. Command already present -> already-set, no duplicate
15
- # 5. No .hooks.UserPromptSubmit -> no-hooks, file untouched
16
- # 6. File absent -> absent
17
- # 7. Malformed JSON -> rewrite-failed, file untouched
18
- # 8. settings.json is an array -> rewrite-failed or no-hooks, file untouched
19
- # 9. reconcile_all_accounts_* -> patches every account.json dir, skips others
20
- # 10. No temp file is ever left behind, on any path
21
- #
22
- # All cases assert the [backfill-2157] status token and the post-state of the file.
23
-
24
- set -u
25
-
26
- LIB="$(cd "$(dirname "$0")/.." && pwd)/account-settings-mailbox-inject.sh"
27
- if [ ! -f "$LIB" ]; then
28
- echo "FAIL: $LIB not found" >&2
29
- exit 1
30
- fi
31
- # shellcheck source=/dev/null
32
- . "$LIB"
33
-
34
- CMD='bash $PLATFORM_ROOT/plugins/admin/hooks/mailbox-inject.sh'
35
- SIB='bash $PLATFORM_ROOT/plugins/admin/hooks/datetime-inject.sh'
36
-
37
- PASS=0
38
- FAIL=0
39
- TMP="$(mktemp -d)"
40
- trap 'rm -rf "$TMP"' EXIT
41
-
42
- ok() { echo "PASS: $1"; PASS=$((PASS+1)); }
43
- bad() { echo "FAIL: $1" >&2; FAIL=$((FAIL+1)); }
44
-
45
- # cmds <file> -> every command string in the UserPromptSubmit array, one per line
46
- cmds() { jq -r '[.hooks.UserPromptSubmit[]?.hooks[]?.command] | .[]' "$1" 2>/dev/null; }
47
- status_of() { printf '%s' "$1" | /usr/bin/sed 's/.*status=//'; }
48
-
49
- # --- 1. array with a sibling hook -> updated, sibling preserved -------------
50
- F="$TMP/a.json"
51
- printf '%s' "{\"hooks\":{\"UserPromptSubmit\":[{\"hooks\":[{\"type\":\"command\",\"command\":\"$SIB\"}]}]}}" > "$F"
52
- OUT=$(ensure_mailbox_inject_hook "$F")
53
- if [ "$(status_of "$OUT")" = "updated" ] \
54
- && [ "$(cmds "$F" | /usr/bin/grep -cF "$CMD")" -eq 1 ] \
55
- && [ "$(cmds "$F" | /usr/bin/grep -cF "$SIB")" -eq 1 ]; then
56
- ok "existing array -> updated, sibling hook preserved"
57
- else
58
- bad "case 1 (out=$OUT cmds=$(cmds "$F" | tr '\n' ' '))"
59
- fi
60
-
61
- # --- 2. re-run -> already-set, byte-identical -------------------------------
62
- B=$(shasum "$F" | /usr/bin/cut -d' ' -f1)
63
- OUT=$(ensure_mailbox_inject_hook "$F")
64
- A=$(shasum "$F" | /usr/bin/cut -d' ' -f1)
65
- if [ "$(status_of "$OUT")" = "already-set" ] && [ "$B" = "$A" ] \
66
- && [ "$(cmds "$F" | /usr/bin/grep -cF "$CMD")" -eq 1 ]; then
67
- ok "re-run -> already-set, file byte-identical, no duplicate"
68
- else
69
- bad "case 2 (out=$OUT before=$B after=$A)"
70
- fi
71
-
72
- # --- 3. empty array -> updated, whole entry created -------------------------
73
- F="$TMP/b.json"; printf '%s' '{"hooks":{"UserPromptSubmit":[]}}' > "$F"
74
- OUT=$(ensure_mailbox_inject_hook "$F")
75
- if [ "$(status_of "$OUT")" = "updated" ] && [ "$(cmds "$F" | /usr/bin/grep -cF "$CMD")" -eq 1 ]; then
76
- ok "empty array -> updated, entry created"
77
- else
78
- bad "case 3 (out=$OUT cmds=$(cmds "$F" | tr '\n' ' '))"
79
- fi
80
-
81
- # --- 4. command already present in a later entry -> already-set, no dupe ----
82
- F="$TMP/c.json"
83
- printf '%s' "{\"hooks\":{\"UserPromptSubmit\":[{\"hooks\":[{\"type\":\"command\",\"command\":\"$SIB\"}]},{\"hooks\":[{\"type\":\"command\",\"command\":\"$CMD\"}]}]}}" > "$F"
84
- OUT=$(ensure_mailbox_inject_hook "$F")
85
- if [ "$(status_of "$OUT")" = "already-set" ] && [ "$(cmds "$F" | /usr/bin/grep -cF "$CMD")" -eq 1 ]; then
86
- ok "command present in a later entry -> already-set, not duplicated"
87
- else
88
- bad "case 4 (out=$OUT cmds=$(cmds "$F" | tr '\n' ' '))"
89
- fi
90
-
91
- # --- 5. no UserPromptSubmit -> no-hooks, untouched --------------------------
92
- F="$TMP/d.json"; printf '%s' '{"permissions":{}}' > "$F"
93
- B=$(shasum "$F" | /usr/bin/cut -d' ' -f1); OUT=$(ensure_mailbox_inject_hook "$F"); A=$(shasum "$F" | /usr/bin/cut -d' ' -f1)
94
- if [ "$(status_of "$OUT")" = "no-hooks" ] && [ "$B" = "$A" ]; then
95
- ok "no UserPromptSubmit array -> no-hooks, file untouched"
96
- else
97
- bad "case 5 (out=$OUT)"
98
- fi
99
-
100
- # --- 6. file absent -> absent ----------------------------------------------
101
- OUT=$(ensure_mailbox_inject_hook "$TMP/does-not-exist.json")
102
- if [ "$(status_of "$OUT")" = "absent" ]; then
103
- ok "file absent -> absent"
104
- else
105
- bad "case 6 (out=$OUT)"
106
- fi
107
-
108
- # --- 7. malformed JSON -> rewrite-failed, untouched -------------------------
109
- F="$TMP/e.json"; printf 'not json at all' > "$F"
110
- B=$(shasum "$F" | /usr/bin/cut -d' ' -f1); OUT=$(ensure_mailbox_inject_hook "$F"); A=$(shasum "$F" | /usr/bin/cut -d' ' -f1)
111
- if [ "$(status_of "$OUT")" = "rewrite-failed" ] && [ "$B" = "$A" ]; then
112
- ok "malformed JSON -> rewrite-failed, file untouched"
113
- else
114
- bad "case 7 (out=$OUT)"
115
- fi
116
-
117
- # --- 8. settings.json is a JSON array -> not patched, untouched -------------
118
- F="$TMP/f.json"; printf '%s' '[]' > "$F"
119
- B=$(shasum "$F" | /usr/bin/cut -d' ' -f1); OUT=$(ensure_mailbox_inject_hook "$F"); A=$(shasum "$F" | /usr/bin/cut -d' ' -f1)
120
- S=$(status_of "$OUT")
121
- if { [ "$S" = "no-hooks" ] || [ "$S" = "rewrite-failed" ]; } && [ "$B" = "$A" ]; then
122
- ok "settings.json that is an array -> not patched, file untouched"
123
- else
124
- bad "case 8 (out=$OUT before=$B after=$A)"
125
- fi
126
-
127
- # --- 9. reconcile walks account dirs only ----------------------------------
128
- R="$TMP/accts"; mkdir -p "$R/one/.claude" "$R/two/.claude" "$R/notanaccount/.claude"
129
- printf '%s' '{}' > "$R/one/account.json"; printf '%s' '{}' > "$R/two/account.json"
130
- for d in one two notanaccount; do
131
- printf '%s' "{\"hooks\":{\"UserPromptSubmit\":[{\"hooks\":[{\"type\":\"command\",\"command\":\"$SIB\"}]}]}}" > "$R/$d/.claude/settings.json"
132
- done
133
- OUT=$(reconcile_all_accounts_mailbox_inject "$R")
134
- if [ "$(printf '%s' "$OUT" | /usr/bin/grep -c 'status=updated')" -eq 2 ] \
135
- && [ "$(cmds "$R/one/.claude/settings.json" | /usr/bin/grep -cF "$CMD")" -eq 1 ] \
136
- && [ "$(cmds "$R/two/.claude/settings.json" | /usr/bin/grep -cF "$CMD")" -eq 1 ] \
137
- && [ "$(cmds "$R/notanaccount/.claude/settings.json" | /usr/bin/grep -cF "$CMD")" -eq 0 ]; then
138
- ok "reconcile patches account.json dirs only"
139
- else
140
- bad "case 9 (out=$OUT)"
141
- fi
142
-
143
- # --- 10. no temp file left behind on any path ------------------------------
144
- if [ "$(find "$TMP" -name '*.2157.tmp' | /usr/bin/wc -l | tr -d ' ')" -eq 0 ]; then
145
- ok "no .2157.tmp file left behind on any path"
146
- else
147
- bad "case 10 (strays: $(find "$TMP" -name '*.2157.tmp'))"
148
- fi
149
-
150
- # --- 11. never returns non-zero, even on the failure paths -----------------
151
- ensure_mailbox_inject_hook "$TMP/e.json" >/dev/null; r1=$?
152
- ensure_mailbox_inject_hook "$TMP/nope.json" >/dev/null; r2=$?
153
- reconcile_all_accounts_mailbox_inject "$TMP/no-such-dir" >/dev/null; r3=$?
154
- if [ "$r1" -eq 0 ] && [ "$r2" -eq 0 ] && [ "$r3" -eq 0 ]; then
155
- ok "every path returns 0, so a caller running set -e is never aborted"
156
- else
157
- bad "case 11 (malformed=$r1 absent=$r2 nodir=$r3)"
158
- fi
159
-
160
- echo "----"
161
- echo "PASS=$PASS FAIL=$FAIL"
162
- if [ $((PASS+FAIL)) -ne 11 ]; then
163
- echo "FAIL: expected 11 cases, ran $((PASS+FAIL))" >&2
164
- exit 1
165
- fi
166
- [ "$FAIL" -eq 0 ]
@@ -1,151 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Regression test for account-settings-pdf-conformance.sh (Task 1929).
3
- #
4
- # The lib retrofits the quote-render PDF-conformance PostToolUse matcher onto
5
- # existing accounts' .claude/settings.json on every install/upgrade. Task 1922
6
- # added the matcher to provision-account-dir.sh, but that writer only runs at
7
- # account-provision time and the upgrade path re-provisions only the house
8
- # account -- so an account provisioned before 1922 keeps a settings.json without
9
- # the browser-pdf-save PostToolUse matcher and never runs the PDF gate (live
10
- # glsmith is such an account).
11
- #
12
- # Covers:
13
- # 1. Stale matcher (browser-pdf-save present, conformance cmd absent) -> updated, appended, idempotent
14
- # 2. Correct matcher (conformance cmd present) -> already-set, byte-identical
15
- # 3. browser-pdf-save matcher absent (broad mcp__.* present) -> updated, matcher added, wildcard untouched
16
- # 4. No .hooks.PostToolUse -> no-hooks, untouched
17
- # 5. File absent -> absent
18
- # 6. Malformed JSON -> rewrite-failed, untouched, no temp
19
- # 7. reconcile_all_accounts_pdf_conformance -> patches every account.json dir, skips others
20
- #
21
- # All cases assert the [backfill-1929] status token and the post-state of the file.
22
-
23
- set -u
24
-
25
- LIB="$(cd "$(dirname "$0")/.." && pwd)/account-settings-pdf-conformance.sh"
26
- if [ ! -f "$LIB" ]; then
27
- echo "FAIL: $LIB not found" >&2
28
- exit 1
29
- fi
30
- # shellcheck source=/dev/null
31
- . "$LIB"
32
-
33
- MATCHER='mcp__plugin_browser_browser__browser-pdf-save'
34
- CONFORMANCE='bash $PLATFORM_ROOT/plugins/admin/hooks/quote-render-pdf-conformance.sh'
35
-
36
- PASS=0
37
- FAIL=0
38
- TMP="$(mktemp -d)"
39
- trap 'rm -rf "$TMP"' EXIT
40
-
41
- ok() { PASS=$((PASS+1)); }
42
- bad() { FAIL=$((FAIL+1)); echo "FAIL: $1" >&2; }
43
-
44
- # settings.json with a PostToolUse browser-pdf-save matcher carrying the given
45
- # command list. Args after $1 are command strings for that matcher.
46
- write_settings() {
47
- local file="$1"; shift
48
- local cmds="" c
49
- for c in "$@"; do
50
- [ -n "$cmds" ] && cmds="$cmds,"
51
- cmds="$cmds{\"type\":\"command\",\"command\":\"$c\"}"
52
- done
53
- mkdir -p "$(dirname "$file")"
54
- cat > "$file" <<EOF
55
- {
56
- "permissions": { "defaultMode": "bypassPermissions", "allow": [], "deny": [] },
57
- "hooks": {
58
- "PostToolUse": [
59
- { "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash \$P/b.sh" } ] },
60
- { "matcher": "$MATCHER", "hooks": [ $cmds ] },
61
- { "matcher": "mcp__.*", "hooks": [ { "type": "command", "command": "bash \$P/missing.sh" } ] }
62
- ]
63
- }
64
- }
65
- EOF
66
- }
67
-
68
- conformance_count() { grep -cF 'quote-render-pdf-conformance.sh' "$1" 2>/dev/null || true; }
69
- pdfmatcher_present() {
70
- jq -e --arg m "$MATCHER" '.hooks.PostToolUse | any(.[]; .matcher == $m)' "$1" >/dev/null 2>&1
71
- }
72
- wildcard_present() {
73
- jq -e '.hooks.PostToolUse | any(.[]; .matcher == "mcp__.*")' "$1" >/dev/null 2>&1
74
- }
75
-
76
- # --- 1. stale matcher -> updated, then idempotent ---------------------------
77
- F="$TMP/stale/.claude/settings.json"
78
- write_settings "$F"
79
- OUT="$(ensure_pdf_conformance_matcher "$F")"
80
- case "$OUT" in *status=updated*) ok;; *) bad "1a stale should be updated, got: $OUT";; esac
81
- [ "$(conformance_count "$F")" -eq 1 ] && ok || bad "1b conformance not appended (count=$(conformance_count "$F"))"
82
- OUT2="$(ensure_pdf_conformance_matcher "$F")"
83
- case "$OUT2" in *status=already-set*) ok;; *) bad "1c re-run should be already-set, got: $OUT2";; esac
84
- [ "$(conformance_count "$F")" -eq 1 ] && ok || bad "1d conformance duplicated on re-run (count=$(conformance_count "$F"))"
85
-
86
- # --- 2. correct matcher -> already-set, byte-identical ----------------------
87
- F="$TMP/good/.claude/settings.json"
88
- write_settings "$F" "$CONFORMANCE"
89
- BEFORE="$(cat "$F")"
90
- OUT="$(ensure_pdf_conformance_matcher "$F")"
91
- case "$OUT" in *status=already-set*) ok;; *) bad "2a good should be already-set, got: $OUT";; esac
92
- [ "$BEFORE" = "$(cat "$F")" ] && ok || bad "2b already-set file was rewritten"
93
-
94
- # --- 3. browser-pdf-save matcher absent (wildcard present) -> updated, added --
95
- F="$TMP/nomatcher/.claude/settings.json"
96
- mkdir -p "$(dirname "$F")"
97
- cat > "$F" <<'EOF'
98
- { "permissions": {}, "hooks": { "PostToolUse": [
99
- { "matcher": "Bash", "hooks": [] },
100
- { "matcher": "mcp__.*", "hooks": [ { "type": "command", "command": "bash $P/missing.sh" } ] }
101
- ] } }
102
- EOF
103
- OUT="$(ensure_pdf_conformance_matcher "$F")"
104
- case "$OUT" in *status=updated*) ok;; *) bad "3a nomatcher should be updated, got: $OUT";; esac
105
- pdfmatcher_present "$F" && ok || bad "3b browser-pdf-save matcher not added"
106
- [ "$(conformance_count "$F")" -eq 1 ] && ok || bad "3c conformance missing after add"
107
- wildcard_present "$F" && ok || bad "3d wildcard matcher was disturbed"
108
- # added matcher carries exactly the one conformance command
109
- if jq -e --arg m "$MATCHER" '.hooks.PostToolUse[] | select(.matcher==$m) | .hooks | length == 1' "$F" >/dev/null 2>&1; then ok; else bad "3e added matcher should carry exactly one command"; fi
110
-
111
- # --- 4. no PostToolUse -> no-hooks, untouched -------------------------------
112
- F="$TMP/nohooks/.claude/settings.json"
113
- mkdir -p "$(dirname "$F")"
114
- echo '{ "permissions": {}, "hooks": {} }' > "$F"
115
- BEFORE="$(cat "$F")"
116
- OUT="$(ensure_pdf_conformance_matcher "$F")"
117
- case "$OUT" in *status=no-hooks*) ok;; *) bad "4a should be no-hooks, got: $OUT";; esac
118
- [ "$BEFORE" = "$(cat "$F")" ] && ok || bad "4b no-hooks file was rewritten"
119
-
120
- # --- 5. file absent -> absent ------------------------------------------------
121
- OUT="$(ensure_pdf_conformance_matcher "$TMP/does-not-exist/.claude/settings.json")"
122
- case "$OUT" in *status=absent*) ok;; *) bad "5 should be absent, got: $OUT";; esac
123
-
124
- # --- 6. malformed JSON -> rewrite-failed, untouched -------------------------
125
- F="$TMP/bad/.claude/settings.json"
126
- mkdir -p "$(dirname "$F")"
127
- printf '{ this is not json ' > "$F"
128
- BEFORE="$(cat "$F")"
129
- OUT="$(ensure_pdf_conformance_matcher "$F")"
130
- case "$OUT" in *status=rewrite-failed*) ok;; *) bad "6a malformed should be rewrite-failed, got: $OUT";; esac
131
- [ "$BEFORE" = "$(cat "$F")" ] && ok || bad "6b malformed file was mutated"
132
- [ ! -e "$F.1929.tmp" ] && ok || bad "6c temp file left behind"
133
-
134
- # --- 7. reconcile_all_accounts_pdf_conformance ------------------------------
135
- ROOT="$TMP/accts"
136
- mkdir -p "$ROOT/acctA/.claude" "$ROOT/acctB/.claude" "$ROOT/notanaccount/.claude"
137
- echo '{"role":"house"}' > "$ROOT/acctA/account.json"
138
- echo '{"role":"client"}' > "$ROOT/acctB/account.json"
139
- # notanaccount has no account.json -> must be skipped
140
- write_settings "$ROOT/acctA/.claude/settings.json"
141
- write_settings "$ROOT/acctB/.claude/settings.json" "$CONFORMANCE"
142
- write_settings "$ROOT/notanaccount/.claude/settings.json"
143
- RECOUT="$(reconcile_all_accounts_pdf_conformance "$ROOT")"
144
- [ "$(conformance_count "$ROOT/acctA/.claude/settings.json")" -eq 1 ] && ok || bad "7a acctA not patched"
145
- [ "$(conformance_count "$ROOT/acctB/.claude/settings.json")" -eq 1 ] && ok || bad "7b acctB disturbed"
146
- [ "$(conformance_count "$ROOT/notanaccount/.claude/settings.json")" -eq 0 ] && ok || bad "7c non-account dir was patched"
147
- case "$RECOUT" in *acctA*status=updated*) ok;; *) bad "7d reconcile did not log acctA updated";; esac
148
-
149
- echo "----------------------------------------"
150
- echo "PASS=$PASS FAIL=$FAIL"
151
- [ "$FAIL" -eq 0 ]
@@ -1,109 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Task 2339 — the backfill must reach an account provisioned before the hook
3
- # existed, must be a byte-identical no-op on a second run, and must never damage
4
- # a file it cannot patch.
5
- set -uo pipefail
6
- DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
- # shellcheck source=../account-settings-public-surface.sh
8
- . "$DIR/../account-settings-public-surface.sh"
9
- PASS=0; FAIL=0
10
- ok() { echo "PASS: $1"; PASS=$((PASS+1)); }
11
- bad() { echo "FAIL: $1" >&2; FAIL=$((FAIL+1)); }
12
- CMD='bash $PLATFORM_ROOT/plugins/admin/hooks/public-tool-surface.sh'
13
-
14
- # A pre-2339 settings file: all three arrays present, none carrying the command.
15
- mkpre() {
16
- local f="$1"
17
- cat > "$f" <<'JSON'
18
- {
19
- "permissions": { "defaultMode": "bypassPermissions", "allow": [], "deny": [] },
20
- "hooks": {
21
- "PreToolUse": [
22
- { "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash $PLATFORM_ROOT/plugins/admin/hooks/quote-engine-gate.sh" } ] },
23
- { "hooks": [ { "type": "command", "command": "bash $PLATFORM_ROOT/plugins/admin/hooks/archive-ingest-surface-gate.sh" } ] }
24
- ],
25
- "UserPromptSubmit": [
26
- { "hooks": [ { "type": "command", "command": "bash $PLATFORM_ROOT/plugins/admin/hooks/datetime-inject.sh" } ] }
27
- ],
28
- "Stop": [
29
- { "hooks": [ { "type": "command", "command": "bash $PLATFORM_ROOT/plugins/admin/hooks/prompt-optimiser-compliance.sh" } ] }
30
- ]
31
- }
32
- }
33
- JSON
34
- }
35
-
36
- D=$(mktemp -d)
37
- F="$D/settings.json"
38
- mkpre "$F"
39
- OUT=$(ensure_public_surface_hook "$F")
40
- if [[ "$OUT" == *"status=updated"* ]]; then ok "a pre-2339 file is updated"; else bad "expected updated, got: $OUT"; fi
41
- if [ "$(jq -r --arg c "$CMD" '[.hooks.PreToolUse[] | select(has("matcher") | not) | .hooks[] | select(.command == $c)] | length' "$F")" = "1" ]; then
42
- ok "the command lands on the matcher-less PreToolUse entry"
43
- else
44
- bad "command not on the matcher-less PreToolUse entry"
45
- fi
46
- if [ "$(jq -r --arg c "$CMD" '[.hooks.PreToolUse[] | select(has("matcher")) | .hooks[] | select(.command == $c)] | length' "$F")" = "0" ]; then
47
- ok "no matcher-scoped entry gained the command"
48
- else
49
- bad "a matcher-scoped entry gained the command"
50
- fi
51
- if [ "$(jq -r --arg c "$CMD" '[.hooks.Stop[].hooks[] | select(.command == $c)] | length' "$F")" = "1" ]; then
52
- ok "the command lands on the Stop entry"
53
- else
54
- bad "command absent from Stop"
55
- fi
56
- if [ "$(jq -r --arg c "$CMD" '[.hooks.UserPromptSubmit[].hooks[] | select(.command == $c)] | length' "$F")" = "1" ]; then
57
- ok "the command lands on the UserPromptSubmit entry"
58
- else
59
- bad "command absent from UserPromptSubmit"
60
- fi
61
-
62
- BEFORE=$(cat "$F")
63
- OUT=$(ensure_public_surface_hook "$F")
64
- if [[ "$OUT" == *"status=already-set"* ]]; then ok "a second run reports already-set"; else bad "expected already-set, got: $OUT"; fi
65
- if [ "$BEFORE" = "$(cat "$F")" ]; then ok "a second run leaves the file byte-identical"; else bad "second run rewrote the file"; fi
66
-
67
- # Missing file.
68
- OUT=$(ensure_public_surface_hook "$D/nope.json")
69
- if [[ "$OUT" == *"status=absent"* ]]; then ok "a missing file reports absent"; else bad "expected absent, got: $OUT"; fi
70
-
71
- # Valid JSON with no hooks block.
72
- echo '{"permissions":{}}' > "$D/nohooks.json"
73
- OUT=$(ensure_public_surface_hook "$D/nohooks.json")
74
- if [[ "$OUT" == *"status=no-hooks"* ]]; then ok "a hookless file reports no-hooks"; else bad "expected no-hooks, got: $OUT"; fi
75
-
76
- # Malformed JSON is reported and left untouched.
77
- printf '{ not json' > "$D/bad.json"
78
- BAD_BEFORE=$(cat "$D/bad.json")
79
- OUT=$(ensure_public_surface_hook "$D/bad.json")
80
- if [[ "$OUT" == *"status=rewrite-failed"* ]]; then ok "a malformed file reports rewrite-failed"; else bad "expected rewrite-failed, got: $OUT"; fi
81
- if [ "$BAD_BEFORE" = "$(cat "$D/bad.json")" ]; then ok "a malformed file is left untouched"; else bad "malformed file was modified"; fi
82
-
83
- # An empty Stop array gains a whole entry.
84
- mkpre "$D/emptystop.json"
85
- jq '.hooks.Stop = []' "$D/emptystop.json" > "$D/es.tmp" && mv "$D/es.tmp" "$D/emptystop.json"
86
- ensure_public_surface_hook "$D/emptystop.json" >/dev/null
87
- if [ "$(jq -r --arg c "$CMD" '[.hooks.Stop[].hooks[] | select(.command == $c)] | length' "$D/emptystop.json")" = "1" ]; then
88
- ok "an empty Stop array gains a whole entry"
89
- else
90
- bad "empty Stop array not handled"
91
- fi
92
-
93
- # A PreToolUse array with no matcher-less entry gains one.
94
- mkpre "$D/nocatchall.json"
95
- jq 'del(.hooks.PreToolUse[] | select(has("matcher") | not))' "$D/nocatchall.json" > "$D/nc.tmp" && mv "$D/nc.tmp" "$D/nocatchall.json"
96
- ensure_public_surface_hook "$D/nocatchall.json" >/dev/null
97
- if [ "$(jq -r --arg c "$CMD" '[.hooks.PreToolUse[] | select(has("matcher") | not) | .hooks[] | select(.command == $c)] | length' "$D/nocatchall.json")" = "1" ]; then
98
- ok "a PreToolUse array with no matcher-less entry gains one"
99
- else
100
- bad "missing catch-all entry not handled"
101
- fi
102
-
103
- echo "----"
104
- echo "PASS=$PASS FAIL=$FAIL"
105
- if [ $((PASS+FAIL)) -ne 13 ]; then
106
- echo "FAIL: expected 13 checks, ran $((PASS+FAIL))" >&2
107
- exit 1
108
- fi
109
- [ "$FAIL" -eq 0 ]