@rubytech/create-maxy-code 0.1.214 → 0.1.219

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 (89) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/mcp-eager/dist/index.d.ts +0 -55
  3. package/payload/platform/lib/mcp-eager/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/mcp-eager/dist/index.js +0 -76
  5. package/payload/platform/lib/mcp-eager/dist/index.js.map +1 -1
  6. package/payload/platform/lib/mcp-eager/src/index.ts +0 -101
  7. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  8. package/payload/platform/plugins/admin/PLUGIN.md +4 -1
  9. package/payload/platform/plugins/admin/hooks/__tests__/hook-emit-stale-lock-ttl.test.sh +102 -0
  10. package/payload/platform/plugins/admin/hooks/__tests__/post-tool-use-agent.test.sh +97 -1
  11. package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-admin-tool-gate.test.sh +55 -74
  12. package/payload/platform/plugins/admin/hooks/lib/hook-emit.sh +58 -0
  13. package/payload/platform/plugins/admin/hooks/post-tool-use-agent.sh +100 -29
  14. package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +0 -55
  15. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.d.ts +2 -0
  16. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js +130 -0
  18. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js.map +1 -0
  19. package/payload/platform/plugins/admin/mcp/dist/index.js +63 -0
  20. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  21. package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.d.ts +14 -0
  22. package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.d.ts.map +1 -0
  23. package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.js +50 -0
  24. package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.js.map +1 -0
  25. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts +28 -0
  26. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts.map +1 -0
  27. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js +68 -0
  28. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js.map +1 -0
  29. package/payload/platform/plugins/admin/skills/capabilities-here/SKILL.md +31 -0
  30. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +39 -30
  31. package/payload/platform/plugins/browser/mcp/dist/index.js +19 -19
  32. package/payload/platform/plugins/browser/mcp/dist/index.js.map +1 -1
  33. package/payload/platform/plugins/docs/references/platform.md +28 -25
  34. package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
  35. package/payload/platform/plugins/email/mcp/dist/index.js +12 -12
  36. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  37. package/payload/platform/plugins/memory/mcp/dist/index.js +8 -8
  38. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  39. package/payload/platform/plugins/url-get/mcp/dist/index.js +2 -2
  40. package/payload/platform/plugins/url-get/mcp/dist/index.js.map +1 -1
  41. package/payload/platform/scripts/check-architecture-skill-no-drift.mjs +89 -42
  42. package/payload/platform/scripts/check-roles-doc-completeness.mjs +96 -0
  43. package/payload/platform/scripts/check-specialist-tool-surface.mjs +1 -1
  44. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  45. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  46. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  47. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +0 -14
  48. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  49. package/payload/platform/services/claude-session-manager/dist/http-server.js +54 -21
  50. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  51. package/payload/platform/services/claude-session-manager/dist/index.js +20 -9
  52. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  53. package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts +21 -0
  54. package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts.map +1 -1
  55. package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js +118 -1
  56. package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js.map +1 -1
  57. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +1 -1
  58. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +1 -1
  59. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +28 -17
  60. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  61. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +110 -60
  62. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  63. package/payload/platform/services/claude-session-manager/dist/rc-life.d.ts +4 -0
  64. package/payload/platform/services/claude-session-manager/dist/rc-life.d.ts.map +1 -0
  65. package/payload/platform/services/claude-session-manager/dist/rc-life.js +16 -0
  66. package/payload/platform/services/claude-session-manager/dist/rc-life.js.map +1 -0
  67. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts +80 -0
  68. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts.map +1 -0
  69. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js +306 -0
  70. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js.map +1 -0
  71. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts +9 -0
  72. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -1
  73. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +6 -2
  74. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -1
  75. package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts +12 -46
  76. package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts.map +1 -1
  77. package/payload/platform/services/claude-session-manager/dist/systemd-scope.js +24 -73
  78. package/payload/platform/services/claude-session-manager/dist/systemd-scope.js.map +1 -1
  79. package/payload/platform/templates/agents/admin/IDENTITY.md +6 -4
  80. package/payload/platform/templates/specialists/agents/content-producer.md +1 -1
  81. package/payload/premium-plugins/writer-craft/mcp/dist/index.d.ts.map +1 -1
  82. package/payload/premium-plugins/writer-craft/mcp/dist/index.js +4 -5
  83. package/payload/premium-plugins/writer-craft/mcp/dist/index.js.map +1 -1
  84. package/payload/premium-plugins/writer-craft/mcp/src/index.ts +4 -5
  85. package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.d.ts +0 -116
  86. package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.d.ts.map +0 -1
  87. package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.js +0 -125
  88. package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.js.map +0 -1
  89. package/payload/premium-plugins/writer-craft/lib/mcp-eager/package.json +0 -7
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env bash
2
- # Task 529admin-tool-gate enforces orchestration-only at the PreToolUse
3
- # hook. Admin-direct invocations of Write/Edit/MultiEdit/Bash/WebFetch/
4
- # WebSearch are refused with the dispatch hint; in-window Task subagents
5
- # pass through.
2
+ # Task 571the orchestration-only block has been removed. Admin's standard
3
+ # tool surface (Write/Edit/MultiEdit/Bash/WebFetch/WebSearch) is callable
4
+ # directly from the admin seat; delegation is a doctrine bias enforced in
5
+ # IDENTITY.md, not a platform refusal. This test pins the post-571 contract:
6
6
  #
7
- # Task 559 the discriminator is `parent_tool_use_id` from the Claude
8
- # Code hook input. Non-empty → subagent (allow). Absent/empty → admin
9
- # direct (block). The earlier `transcript_path` glob was broken in
10
- # Claude Code 2.1.x because subagent hooks receive the parent session's
11
- # transcript_path, never one under `*/subagents/agent-*.jsonl`.
7
+ # - Admin-direct Bash/Edit/Write/WebFetch all return exit 0 with no
8
+ # `reason=orchestration-only` or `reason=specialist-owned` line.
9
+ # - The resource-specific hook blocks (code-dir, entitlement, account.json,
10
+ # pending-actions, base64-guard, approval-queue) are unaffected and live
11
+ # in their own dedicated tests.
12
+ #
13
+ # Reintroducing the orchestration-only block requires a doctrine flip — this
14
+ # test fails first and surfaces the change loudly.
12
15
 
13
16
  set -u
14
17
 
@@ -30,97 +33,75 @@ PASS=0; FAIL=0
30
33
  pass() { PASS=$((PASS+1)); echo "PASS: $1"; }
31
34
  fail() { FAIL=$((FAIL+1)); echo "FAIL: $1" >&2; }
32
35
 
33
- # The hook receives the parent session's transcript_path even on subagent
34
- # calls in Claude Code 2.1.x. The fixture mirrors production.
35
36
  SESSION_TRANSCRIPT="/tmp/sess/44283ae1-2b82-4bd7-b8af-99a11141ed4b.jsonl"
36
- SUBAGENT_PARENT_ID="toolu_01QFC4W32wFUyHy6WJMNjHf6"
37
37
 
38
- run() {
39
- local tool="$1" parent_id="$2" expected_rc="$3" expected_pattern="$4" label="$5"
38
+ # run_admin_direct <tool> <expected_rc> <label>
39
+ # Pipes a minimal PreToolUse payload to the hook with MAXY_SESSION_ROLE=admin
40
+ # (the realistic admin spawn shape) and asserts the rc and that no
41
+ # orchestration-only / specialist-owned refusal line appears on stderr.
42
+ run_admin_direct() {
43
+ local tool="$1" expected_rc="$2" label="$3"
40
44
  local input_json
41
45
  input_json=$(python3 -c '
42
46
  import json, sys
43
- payload = {
47
+ print(json.dumps({
44
48
  "hook_event_name": "PreToolUse",
45
49
  "tool_name": sys.argv[1],
46
- "tool_input": {"file_path": "/tmp/x", "content": "x", "command": "echo hi"},
50
+ "tool_input": {"file_path": "/tmp/x", "content": "x", "command": "echo hi", "url": "https://example.com"},
47
51
  "transcript_path": sys.argv[2],
48
- }
49
- parent = sys.argv[3]
50
- if parent:
51
- payload["parent_tool_use_id"] = parent
52
- print(json.dumps(payload, separators=(",", ":")))
53
- ' "$tool" "$SESSION_TRANSCRIPT" "$parent_id")
52
+ }, separators=(",", ":")))
53
+ ' "$tool" "$SESSION_TRANSCRIPT")
54
54
  local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
55
55
  local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
56
- printf '%s' "$input_json" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
56
+ printf '%s' "$input_json" | \
57
+ env -u MAXY_SPECIALIST MAXY_SESSION_ROLE=admin \
58
+ bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
57
59
  local rc=$?
58
60
  if [[ "$rc" -ne "$expected_rc" ]]; then
59
61
  fail "$label: expected exit $expected_rc, got $rc. Stderr: $(cat "$stderr_file")"
60
62
  return
61
63
  fi
62
- if [[ -n "$expected_pattern" ]] && ! grep -qE "$expected_pattern" "$stderr_file"; then
63
- fail "$label: stderr missing pattern '$expected_pattern'. Got: $(cat "$stderr_file")"
64
+ if grep -qE 'reason=orchestration-only|reason=specialist-owned|reason=missing-marker' "$stderr_file"; then
65
+ fail "$label: stderr carries a removed-gate reason line. Got: $(cat "$stderr_file")"
64
66
  return
65
67
  fi
66
68
  pass "$label"
67
69
  }
68
70
 
69
- # ── Admin-direct refusals (no parent_tool_use_id) ───────────────────────
70
- run Write "" 2 '\[admin-tool-gate\] role=admin parent_tool_use_id=- specialist=- tool=Write decision=block.*owner=content-producer' \
71
- "Test 1: admin-direct Write rejected with content-producer dispatch hint"
72
- run Edit "" 2 '\[admin-tool-gate\] role=admin .*tool=Edit decision=block.*owner=content-producer' \
73
- "Test 2: admin-direct Edit rejected"
74
- run MultiEdit "" 2 '\[admin-tool-gate\] role=admin .*tool=MultiEdit decision=block.*owner=content-producer' \
75
- "Test 3: admin-direct MultiEdit rejected"
76
- run Bash "" 2 '\[admin-tool-gate\] role=admin .*tool=Bash decision=block' \
77
- "Test 4: admin-direct Bash rejected"
78
- run WebFetch "" 2 '\[admin-tool-gate\] role=admin .*tool=WebFetch decision=block.*owner=research-assistant' \
79
- "Test 5: admin-direct WebFetch rejected"
80
- run WebSearch "" 2 '\[admin-tool-gate\] role=admin .*tool=WebSearch decision=block.*owner=research-assistant' \
81
- "Test 6: admin-direct WebSearch rejected"
82
-
83
- # ── In-window subagent passthrough (parent_tool_use_id non-empty) ───────
84
- run Write "$SUBAGENT_PARENT_ID" 0 "\[admin-tool-gate\] role=subagent parent_tool_use_id=${SUBAGENT_PARENT_ID} .*tool=Write decision=allow" \
85
- "Test 7: subagent Write passes through"
86
- run Edit "$SUBAGENT_PARENT_ID" 0 '\[admin-tool-gate\] role=subagent .*tool=Edit decision=allow' \
87
- "Test 8: subagent Edit passes through"
88
- run Bash "$SUBAGENT_PARENT_ID" 0 '\[admin-tool-gate\] role=subagent .*tool=Bash decision=allow' \
89
- "Test 9: subagent Bash passes through (Task 559 regression — coding-assistant git pull)"
90
- run WebFetch "$SUBAGENT_PARENT_ID" 0 '\[admin-tool-gate\] role=subagent .*tool=WebFetch decision=allow' \
91
- "Test 10: subagent WebFetch passes through"
92
- run MultiEdit "$SUBAGENT_PARENT_ID" 0 '\[admin-tool-gate\] role=subagent .*tool=MultiEdit decision=allow' \
93
- "Test 11: subagent MultiEdit passes through"
71
+ # ── Admin-direct passthrough (the Task 571 regression boundary) ──────────
72
+ run_admin_direct Bash 0 "Test 1: admin-direct Bash passes through"
73
+ run_admin_direct Edit 0 "Test 2: admin-direct Edit passes through (path /tmp/x is outside protected dirs)"
74
+ run_admin_direct Write 0 "Test 3: admin-direct Write passes through"
75
+ run_admin_direct MultiEdit 0 "Test 4: admin-direct MultiEdit passes through"
76
+ run_admin_direct WebFetch 0 "Test 5: admin-direct WebFetch passes through"
77
+ run_admin_direct WebSearch 0 "Test 6: admin-direct WebSearch passes through"
78
+ run_admin_direct Read 0 "Test 7: admin-direct Read passes through"
94
79
 
95
- # ── Unrelated tools untouched ───────────────────────────────────────────
96
- run Read "" 0 '' \
97
- "Test 12: admin-direct Read passes (Read is admin-keep-list)"
98
-
99
- # ── Task 560: admin-direct block emits propagation record ──────────────
100
- # Uses the Task 559 classifier (no parent_tool_use_id → admin-direct).
101
- PROP_TMPDIR=$(mktemp -d); TMPFILES+=("$PROP_TMPDIR")
102
- export MAXY_HOOK_BUFFER_DIR="$PROP_TMPDIR/buffers"
103
- export MAXY_HOOK_SERVER_LOG="$PROP_TMPDIR/server.log"
104
- SESSION="task560-test-sess"
105
- PROP_INPUT=$(python3 -c '
80
+ # ── Resource-specific blocks unaffected: protected code-dir Write still refuses ──
81
+ # Verifies the deletion was surgical — code-dir-protection-ui still fires.
82
+ PROTECTED_INPUT=$(python3 -c '
106
83
  import json, sys
107
- print(json.dumps({"hook_event_name":"PreToolUse","tool_name":"Bash",
108
- "tool_input":{"command":"ls"},"transcript_path":sys.argv[1],
109
- "session_id":sys.argv[2]}, separators=(",",":")))
110
- ' "$SESSION_TRANSCRIPT" "$SESSION")
111
- printf '%s' "$PROP_INPUT" | bash "$HOOK" admin >/dev/null 2>/dev/null
112
- BUF="$MAXY_HOOK_BUFFER_DIR/$SESSION.jsonl"
113
- if [[ -f "$BUF" ]] && grep -q '"hookName":"admin-tool-gate"' "$BUF" \
114
- && grep -q '"decision":"block"' "$BUF" \
115
- && grep -q '"reason":"orchestration-only"' "$BUF"; then
116
- pass "Test 13b: admin-direct Bash block emits Task 560 propagation record"
84
+ print(json.dumps({
85
+ "hook_event_name": "PreToolUse",
86
+ "tool_name": "Write",
87
+ "tool_input": {"file_path": "/srv/maxy/platform/ui/app/page.tsx", "content": "x"},
88
+ "transcript_path": sys.argv[1],
89
+ }, separators=(",", ":")))
90
+ ' "$SESSION_TRANSCRIPT")
91
+ PROT_STDOUT=$(mktemp); TMPFILES+=("$PROT_STDOUT")
92
+ PROT_STDERR=$(mktemp); TMPFILES+=("$PROT_STDERR")
93
+ printf '%s' "$PROTECTED_INPUT" | \
94
+ env -u MAXY_SPECIALIST MAXY_SESSION_ROLE=admin \
95
+ bash "$HOOK" admin >"$PROT_STDOUT" 2>"$PROT_STDERR"
96
+ PROT_RC=$?
97
+ if [[ "$PROT_RC" -eq 2 ]] && grep -q 'cannot modify platform UI source' "$PROT_STDERR"; then
98
+ pass "Test 8: admin-direct Write to platform/ui/ still blocked (resource-specific block intact)"
117
99
  else
118
- fail "Test 13b: propagation record missing/malformed $(cat "$BUF" 2>/dev/null || echo '<no buffer>')"
100
+ fail "Test 8: expected platform/ui code-dir block, got rc=$PROT_RC stdout=$(cat "$PROT_STDOUT") stderr=$(cat "$PROT_STDERR")"
119
101
  fi
120
- unset MAXY_HOOK_BUFFER_DIR MAXY_HOOK_SERVER_LOG
121
102
 
122
103
  echo
123
- echo "──────── pre-tool-use admin-tool-gate test summary ────────"
104
+ echo "──────── pre-tool-use admin-tool-gate test summary (post-Task 571) ────────"
124
105
  echo "PASS: $PASS"
125
106
  echo "FAIL: $FAIL"
126
107
 
@@ -9,6 +9,55 @@
9
9
  : "${MAXY_HOOK_BUFFER_DIR:=$HOME/.maxy-code/logs/hook-decisions}"
10
10
  : "${MAXY_HOOK_SERVER_LOG:=$HOME/.maxy-code/logs/server.log}"
11
11
  : "${MAXY_HOOK_STDERR_CAP:=4096}"
12
+ # Task 563 — mkdir-based mutex serialises emitter append vs drainer rename.
13
+ # 100 attempts × 10ms = ~1s ceiling. Portable across Linux (Pi) and macOS
14
+ # (tests) without flock(1).
15
+ : "${MAXY_HOOK_LOCK_ATTEMPTS:=100}"
16
+ : "${MAXY_HOOK_LOCK_SLEEP:=0.01}"
17
+ # Task 569 — TTL for orphaned lockdirs left behind when a holder dies
18
+ # between `mkdir lock` and `rmdir lock` (SIGKILL, OOM, systemd restart).
19
+ # Default 5s is far longer than the legitimate hold (microseconds for a
20
+ # rename or append) and shorter than operator-noticeable propagation lag.
21
+ : "${MAXY_HOOK_LOCK_TTL_SECONDS:=5}"
22
+
23
+ # _hook_lockdir_age_seconds <lockdir> — prints integer age in seconds, or
24
+ # -1 if the lockdir cannot be stat'd. Portable across Linux (`stat -c`)
25
+ # and macOS (`stat -f`).
26
+ _hook_lockdir_age_seconds() {
27
+ local lockdir="$1" mtime now
28
+ mtime=$(stat -c %Y "$lockdir" 2>/dev/null || stat -f %m "$lockdir" 2>/dev/null)
29
+ [[ -z "$mtime" ]] && { echo -1; return; }
30
+ now=$(date +%s)
31
+ echo $((now - mtime))
32
+ }
33
+
34
+ # _hook_acquire_lock <lockdir> — returns 0 on success, 1 on timeout.
35
+ # On each mkdir failure, if the lockdir is older than
36
+ # MAXY_HOOK_LOCK_TTL_SECONDS, the orphaned holder is presumed dead: the
37
+ # lockdir is force-removed, a stale-lock-reclaimed observability line is
38
+ # emitted, and one mkdir retry is attempted before resuming the spin.
39
+ _hook_acquire_lock() {
40
+ local lockdir="$1" i=0 age
41
+ while ! mkdir "$lockdir" 2>/dev/null; do
42
+ age=$(_hook_lockdir_age_seconds "$lockdir")
43
+ if [[ "$age" -ge "$MAXY_HOOK_LOCK_TTL_SECONDS" ]]; then
44
+ rmdir "$lockdir" 2>/dev/null || rm -rf "$lockdir" 2>/dev/null
45
+ mkdir -p "$(dirname "$MAXY_HOOK_SERVER_LOG")" 2>/dev/null
46
+ printf '[hook-propagate-error] reason=stale-lock-reclaimed lockdir=%s ageSeconds=%s\n' \
47
+ "$lockdir" "$age" >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null || true
48
+ mkdir "$lockdir" 2>/dev/null && return 0
49
+ fi
50
+ i=$((i+1))
51
+ [[ "$i" -ge "$MAXY_HOOK_LOCK_ATTEMPTS" ]] && return 1
52
+ sleep "$MAXY_HOOK_LOCK_SLEEP" 2>/dev/null || true
53
+ done
54
+ return 0
55
+ }
56
+
57
+ # _hook_release_lock <lockdir>
58
+ _hook_release_lock() {
59
+ rmdir "$1" 2>/dev/null || true
60
+ }
12
61
 
13
62
  # hook_emit_decision <agentId> <hookName> <toolName> <decision> <reason>
14
63
  # <stderrPayload> <exitCode> <durationMs>
@@ -52,12 +101,21 @@ PY
52
101
 
53
102
  mkdir -p "$MAXY_HOOK_BUFFER_DIR" 2>/dev/null
54
103
  local buf="$MAXY_HOOK_BUFFER_DIR/$agentId.jsonl"
104
+ local lock="${buf}.lock"
105
+ if ! _hook_acquire_lock "$lock"; then
106
+ mkdir -p "$(dirname "$MAXY_HOOK_SERVER_LOG")" 2>/dev/null
107
+ printf '[hook-propagate-error] reason=lock-timeout agentId=%s hook=%s\n' \
108
+ "$agentId" "$hookName" >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null
109
+ return 0
110
+ fi
55
111
  if ! printf '%s\n' "$rec" >>"$buf" 2>/dev/null; then
112
+ _hook_release_lock "$lock"
56
113
  mkdir -p "$(dirname "$MAXY_HOOK_SERVER_LOG")" 2>/dev/null
57
114
  printf '[hook-propagate-error] reason=buffer-write agentId=%s hook=%s\n' \
58
115
  "$agentId" "$hookName" >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null
59
116
  return 0
60
117
  fi
118
+ _hook_release_lock "$lock"
61
119
 
62
120
  local bytesIn="${#stderrPayload}"
63
121
  local bytesOut="${#rec}"
@@ -1,15 +1,35 @@
1
1
  #!/usr/bin/env bash
2
2
  # Task 560 — PostToolUse hook scoped to tool_name=Agent in the admin role.
3
- # Drains any subagent hook-decision buffers modified since this parent's
4
- # previous fire, prints each record as a [hook-propagate] line to stdout
5
- # (Claude Code attaches the stdout to the parent JSONL as a hook_success
6
- # attachment), and emits one [hook-propagate-census] line. N != M in the
7
- # census is the propagation regression signal.
3
+ # Drains every subagent hook-decision buffer in the dir, prints each record
4
+ # as a [hook-propagate] line to stdout (Claude Code attaches the stdout to
5
+ # the parent JSONL as a hook_success attachment), and emits one
6
+ # [hook-propagate-census] line. N != M in the census is the propagation
7
+ # regression signal.
8
+ #
9
+ # Task 563 — concurrency hardening:
10
+ # - Atomic claim before read. Each buffer is renamed under a mkdir mutex
11
+ # to a per-fire `claimed/<basename>-<epoch>-<pid>.jsonl` path. The lock
12
+ # is held only for the rename syscall; reading happens against the
13
+ # renamed inode, which no emitter can address. The race window between
14
+ # the drainer's read and the buffer rotation is closed.
15
+ # - No cursor. The cursor file was load-bearing only because consumed
16
+ # buffers could be re-seen; with atomic claim the file's presence in
17
+ # the directory is the unambiguous signal. Dropping the cursor closes
18
+ # two further races (concurrent-drainer cursor write, shared
19
+ # `.unknown.cursor` across broken-stdin fires).
20
+ # - Unknown-parent observability. When stdin's `session_id` is missing
21
+ # or unparseable, an explicit `[hook-propagate] parentSession=unknown
22
+ # reason=session-id-missing` line is emitted to both stdout and
23
+ # server.log so the broken-stdin path is no longer silent.
8
24
 
9
25
  set -uo pipefail
10
26
 
11
27
  : "${MAXY_HOOK_BUFFER_DIR:=$HOME/.maxy-code/logs/hook-decisions}"
12
28
  : "${MAXY_HOOK_SERVER_LOG:=$HOME/.maxy-code/logs/server.log}"
29
+ : "${MAXY_HOOK_LOCK_ATTEMPTS:=100}"
30
+ : "${MAXY_HOOK_LOCK_SLEEP:=0.01}"
31
+ # Task 569 — TTL for orphaned lockdirs (see hook-emit.sh for rationale).
32
+ : "${MAXY_HOOK_LOCK_TTL_SECONDS:=5}"
13
33
 
14
34
  if [ -t 0 ]; then
15
35
  # No stdin — no correlation possible. Fail-open.
@@ -19,61 +39,112 @@ INPUT=$(cat)
19
39
  PARENT_SESSION=$(printf '%s' "$INPUT" | python3 -c '
20
40
  import sys, json
21
41
  try:
22
- print(json.load(sys.stdin).get("session_id", "") or "unknown")
42
+ sid = json.load(sys.stdin).get("session_id") or ""
43
+ print(sid if sid else "unknown")
23
44
  except Exception:
24
45
  print("unknown")
25
46
  ' 2>/dev/null || echo "unknown")
26
47
 
27
- CURSOR_FILE="$MAXY_HOOK_BUFFER_DIR/.${PARENT_SESSION}.cursor"
28
- mkdir -p "$MAXY_HOOK_BUFFER_DIR/consumed" 2>/dev/null
29
-
30
- PREV_CURSOR=0
31
- if [[ -f "$CURSOR_FILE" ]]; then
32
- PREV_CURSOR=$(cat "$CURSOR_FILE" 2>/dev/null || echo 0)
33
- [[ "$PREV_CURSOR" =~ ^[0-9]+$ ]] || PREV_CURSOR=0
48
+ if [[ "$PARENT_SESSION" == "unknown" ]]; then
49
+ # Conservative play (Task 563 brief option C): emit observability and
50
+ # exit without draining. A broken-stdin fire has no business processing
51
+ # buffers — the next valid parent fire will pick them up.
52
+ mkdir -p "$(dirname "$MAXY_HOOK_SERVER_LOG")" 2>/dev/null
53
+ printf '[hook-propagate] parentSession=unknown reason=session-id-missing\n'
54
+ printf '[hook-propagate] parentSession=unknown reason=session-id-missing\n' \
55
+ >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null || true
56
+ exit 0
34
57
  fi
35
- NOW_EPOCH=$(date +%s)
36
58
 
59
+ mkdir -p "$MAXY_HOOK_BUFFER_DIR/claimed" "$MAXY_HOOK_BUFFER_DIR/consumed" 2>/dev/null
60
+
61
+ # Sweep orphaned `.<session>.cursor` files left behind by an older
62
+ # version of this script. The cursor mechanism is gone; without this
63
+ # they would persist forever as dir noise.
64
+ rm -f "$MAXY_HOOK_BUFFER_DIR"/.*.cursor 2>/dev/null || true
65
+
66
+ NOW_EPOCH=$(date +%s)
37
67
  N_OBSERVED=0
38
68
  N_EMITTED=0
69
+
70
+ # Task 569 — same stale-lockdir TTL reclamation as hook-emit.sh.
71
+ _drain_lockdir_age_seconds() {
72
+ local lockdir="$1" mtime now
73
+ mtime=$(stat -c %Y "$lockdir" 2>/dev/null || stat -f %m "$lockdir" 2>/dev/null)
74
+ [[ -z "$mtime" ]] && { echo -1; return; }
75
+ now=$(date +%s)
76
+ echo $((now - mtime))
77
+ }
78
+
79
+ _drain_acquire_lock() {
80
+ local lockdir="$1" i=0 age
81
+ while ! mkdir "$lockdir" 2>/dev/null; do
82
+ age=$(_drain_lockdir_age_seconds "$lockdir")
83
+ if [[ "$age" -ge "$MAXY_HOOK_LOCK_TTL_SECONDS" ]]; then
84
+ rmdir "$lockdir" 2>/dev/null || rm -rf "$lockdir" 2>/dev/null
85
+ mkdir -p "$(dirname "$MAXY_HOOK_SERVER_LOG")" 2>/dev/null
86
+ printf '[hook-propagate-error] reason=stale-lock-reclaimed lockdir=%s ageSeconds=%s\n' \
87
+ "$lockdir" "$age" >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null || true
88
+ mkdir "$lockdir" 2>/dev/null && return 0
89
+ fi
90
+ i=$((i+1))
91
+ [[ "$i" -ge "$MAXY_HOOK_LOCK_ATTEMPTS" ]] && return 1
92
+ sleep "$MAXY_HOOK_LOCK_SLEEP" 2>/dev/null || true
93
+ done
94
+ return 0
95
+ }
96
+
39
97
  shopt -s nullglob
40
98
  for buf in "$MAXY_HOOK_BUFFER_DIR"/*.jsonl; do
41
99
  [[ -f "$buf" ]] || continue
42
100
  case "$(basename "$buf")" in
43
101
  .*) continue;;
44
102
  esac
45
- MTIME=$(stat -f %m "$buf" 2>/dev/null || stat -c %Y "$buf" 2>/dev/null || echo 0)
46
- if [[ "$MTIME" -le "$PREV_CURSOR" ]]; then
103
+ lock="${buf}.lock"
104
+ if ! _drain_acquire_lock "$lock"; then
105
+ mkdir -p "$(dirname "$MAXY_HOOK_SERVER_LOG")" 2>/dev/null
106
+ printf '[hook-propagate-error] reason=lock-timeout parentSession=%s buffer=%s\n' \
107
+ "$PARENT_SESSION" "$(basename "$buf")" >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null || true
108
+ continue
109
+ fi
110
+ # Re-check existence under lock — a peer drainer may have claimed it
111
+ # between our `for` iteration and our lock acquisition.
112
+ if [[ ! -f "$buf" ]]; then
113
+ rmdir "$lock" 2>/dev/null || true
114
+ continue
115
+ fi
116
+ CLAIMED="$MAXY_HOOK_BUFFER_DIR/claimed/$(basename "$buf" .jsonl)-${NOW_EPOCH}-$$.jsonl"
117
+ if ! mv "$buf" "$CLAIMED" 2>/dev/null; then
118
+ rmdir "$lock" 2>/dev/null || true
119
+ mkdir -p "$(dirname "$MAXY_HOOK_SERVER_LOG")" 2>/dev/null
120
+ printf '[hook-propagate-error] reason=claim-mv parentSession=%s buffer=%s\n' \
121
+ "$PARENT_SESSION" "$(basename "$buf")" >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null || true
47
122
  continue
48
123
  fi
49
- # Ground-truth observed count: non-empty lines in the buffer file. The
50
- # printf-success M_EMITTED is incremented independently below, so a
124
+ rmdir "$lock" 2>/dev/null || true
125
+
126
+ # Ground-truth observed count: non-empty lines in the claimed file. The
127
+ # printf-success N_EMITTED is incremented independently below, so a
51
128
  # printf failure (SIGPIPE on a closed downstream pipe, EIO on stdout)
52
129
  # produces N_OBSERVED > N_EMITTED — the propagation regression signal
53
130
  # the census promises. The two counters must not be bumped together.
54
- BUF_LINES=$(grep -c . "$buf" 2>/dev/null || echo 0)
131
+ BUF_LINES=$(grep -c . "$CLAIMED" 2>/dev/null || echo 0)
55
132
  N_OBSERVED=$((N_OBSERVED + BUF_LINES))
56
133
  while IFS= read -r line || [[ -n "$line" ]]; do
57
134
  [[ -z "$line" ]] && continue
58
135
  if printf '[hook-propagate] %s\n' "$line" 2>/dev/null; then
59
136
  N_EMITTED=$((N_EMITTED+1))
60
137
  fi
61
- done <"$buf"
62
- TARGET="$MAXY_HOOK_BUFFER_DIR/consumed/$(basename "$buf" .jsonl)-${NOW_EPOCH}.jsonl"
63
- if ! mv "$buf" "$TARGET" 2>/dev/null; then
138
+ done <"$CLAIMED"
139
+ TARGET="$MAXY_HOOK_BUFFER_DIR/consumed/$(basename "$CLAIMED")"
140
+ if ! mv "$CLAIMED" "$TARGET" 2>/dev/null; then
64
141
  mkdir -p "$(dirname "$MAXY_HOOK_SERVER_LOG")" 2>/dev/null
65
142
  printf '[hook-propagate-error] reason=consumed-mv parentSession=%s buffer=%s\n' \
66
- "$PARENT_SESSION" "$(basename "$buf")" >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null || true
143
+ "$PARENT_SESSION" "$(basename "$CLAIMED")" >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null || true
67
144
  fi
68
145
  done
69
146
  shopt -u nullglob
70
147
 
71
- if ! printf '%s' "$NOW_EPOCH" >"$CURSOR_FILE" 2>/dev/null; then
72
- mkdir -p "$(dirname "$MAXY_HOOK_SERVER_LOG")" 2>/dev/null
73
- printf '[hook-propagate-error] reason=cursor-write parentSession=%s cursor=%s\n' \
74
- "$PARENT_SESSION" "$CURSOR_FILE" >>"$MAXY_HOOK_SERVER_LOG" 2>/dev/null || true
75
- fi
76
-
77
148
  printf '[hook-propagate-census] parentSession=%s subagentHooksObserved=%d attachmentsEmitted=%d\n' \
78
149
  "$PARENT_SESSION" "$N_OBSERVED" "$N_EMITTED"
79
150
 
@@ -43,61 +43,6 @@ except Exception:
43
43
  # ---------------------------------------------------------------------------
44
44
  if [ "$AGENT_TYPE" = "admin" ]; then
45
45
 
46
- # ── Orchestration-only authoring + research block (Task 529, 559) ────────
47
- # The admin seat is orchestration + clarification + delivery only.
48
- # Write/Edit/MultiEdit/Bash/WebFetch/WebSearch are specialist-owned —
49
- # content-producer for authoring, research-assistant for research, the
50
- # shell tools for whichever specialist owns the workflow that needs them.
51
- # In-window Task subagents must keep these tools (Task 222 regression
52
- # class).
53
- #
54
- # Discriminator (Task 559): Claude Code passes `parent_tool_use_id` in
55
- # the hook input JSON when the call originates from a subagent (the id
56
- # of the parent's Task tool_use). Admin-direct calls have it absent or
57
- # empty. Earlier versions used the `transcript_path` glob, but Claude
58
- # Code 2.1.x passes the parent session's transcript_path to subagent
59
- # hooks, so the glob never matched and every specialist subagent Bash
60
- # was blocked with the admin orchestration-only wording.
61
- #
62
- # Admin direct (no parent_tool_use_id) → exit 2 with dispatch hint.
63
- # Subagent (parent_tool_use_id non-empty) → exit 0 (passthrough).
64
- case "$TOOL_NAME" in
65
- Write|Edit|MultiEdit|Bash|WebFetch|WebSearch)
66
- PARENT_TOOL_USE_ID=$(echo "$INPUT" | python3 -c '
67
- import sys, json
68
- try:
69
- v = json.load(sys.stdin).get("parent_tool_use_id", "")
70
- print(v if isinstance(v, str) else "")
71
- except Exception:
72
- print("")
73
- ' 2>/dev/null)
74
- SPECIALIST="${MAXY_SPECIALIST:-}"
75
- if [ -n "$PARENT_TOOL_USE_ID" ]; then
76
- # In-window subagent — passthrough. Tool name resolves on the
77
- # subagent's own frontmatter `tools:` allowlist downstream.
78
- echo "[admin-tool-gate] role=subagent parent_tool_use_id=${PARENT_TOOL_USE_ID} specialist=${SPECIALIST:--} tool=${TOOL_NAME} decision=allow source=hook-passthrough" >&2
79
- else
80
- # Admin direct invocation — refuse with dispatch hint.
81
- case "$TOOL_NAME" in
82
- Write|Edit|MultiEdit)
83
- OWNER="content-producer"
84
- ;;
85
- Bash)
86
- OWNER="the specialist that owns the workflow needing shell access"
87
- ;;
88
- WebFetch|WebSearch)
89
- OWNER="research-assistant"
90
- ;;
91
- esac
92
- echo "[admin-tool-gate] role=admin parent_tool_use_id=- specialist=${SPECIALIST:--} tool=${TOOL_NAME} decision=block reason=orchestration-only owner=${OWNER}" >&2
93
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
94
- "orchestration-only" \
95
- "Blocked: \`${TOOL_NAME}\` is not callable from the admin seat. The admin seat is orchestration + clarification + delivery only — authoring, shell work, and web research are owned by specialists.
96
- Dispatch the work to ${OWNER} via the Agent tool."
97
- fi
98
- ;;
99
- esac
100
-
101
46
  # ── Code directory protection ────────────────────────────────────────────
102
47
  case "$TOOL_NAME" in
103
48
  Write|Edit)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=capabilities-here.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities-here.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/capabilities-here.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { resolveCapabilitiesHere } from "../tools/capabilities-here.js";
6
+ function writeFile(path, body) {
7
+ mkdirSync(join(path, ".."), { recursive: true });
8
+ writeFileSync(path, body);
9
+ }
10
+ function seedTree(root, spec) {
11
+ const accountConfigPath = join(root, "accounts", spec.accountId, "account.json");
12
+ const brandConfigPath = join(root, "platform", "config", "brand.json");
13
+ const platformRoot = join(root, "platform");
14
+ const premiumPluginsRoot = join(root, "premium-plugins");
15
+ const claudeConfigDir = join(root, "claude-config");
16
+ writeFile(accountConfigPath, JSON.stringify({
17
+ accountId: spec.accountId,
18
+ enabledPlugins: spec.enabledPlugins ?? [],
19
+ tier: spec.tier,
20
+ adminModel: spec.adminModel,
21
+ publicModel: spec.publicModel,
22
+ }));
23
+ writeFile(brandConfigPath, JSON.stringify({
24
+ productName: spec.productName,
25
+ shipsPremiumBundles: spec.shipsPremiumBundles ?? [],
26
+ }));
27
+ const specialistsDir = join(platformRoot, "templates", "specialists", "agents");
28
+ mkdirSync(specialistsDir, { recursive: true });
29
+ for (const name of spec.platformSpecialists) {
30
+ writeFileSync(join(specialistsDir, `${name}.md`), `---\nname: ${name}\n---\n`);
31
+ }
32
+ for (const [bundle, names] of Object.entries(spec.premiumSpecialists ?? {})) {
33
+ const dir = join(premiumPluginsRoot, bundle, "agents");
34
+ mkdirSync(dir, { recursive: true });
35
+ for (const name of names) {
36
+ writeFileSync(join(dir, `${name}.md`), `---\nname: ${name}\n---\n`);
37
+ }
38
+ }
39
+ const agentsDir = join(claudeConfigDir, "agents");
40
+ mkdirSync(agentsDir, { recursive: true });
41
+ for (const name of spec.registeredAgents ?? []) {
42
+ writeFileSync(join(agentsDir, `${name}.md`), `---\nname: ${name}\n---\n`);
43
+ }
44
+ return {
45
+ input: {
46
+ accountId: spec.accountId,
47
+ accountConfigPath,
48
+ brandConfigPath,
49
+ platformRoot,
50
+ premiumPluginsRoot,
51
+ claudeConfigDir,
52
+ },
53
+ };
54
+ }
55
+ describe("resolveCapabilitiesHere", () => {
56
+ let root;
57
+ beforeEach(() => { root = mkdtempSync(join(tmpdir(), "cap-here-")); });
58
+ afterEach(() => { rmSync(root, { recursive: true, force: true }); });
59
+ it("returns Maxy install set with no premium bundles", async () => {
60
+ const { input } = seedTree(root, {
61
+ accountId: "acct-maxy",
62
+ enabledPlugins: ["business-assistant", "sales", "teaching"],
63
+ tier: "pro",
64
+ adminModel: "claude-opus-4-7",
65
+ publicModel: "claude-haiku-4-5",
66
+ productName: "Maxy",
67
+ shipsPremiumBundles: [],
68
+ platformSpecialists: ["personal-assistant", "project-manager", "librarian"],
69
+ registeredAgents: ["plugin:admin:librarian", "plugin:admin:project-manager"],
70
+ });
71
+ const result = await resolveCapabilitiesHere(input, "2026-05-31T07:00:00Z");
72
+ expect(result.accountId).toBe("acct-maxy");
73
+ expect(result.tier).toBe("pro");
74
+ expect(result.enabledPlugins).toEqual(["business-assistant", "sales", "teaching"]);
75
+ expect(result.installedPlatformSpecialists).toEqual(["librarian", "personal-assistant", "project-manager"]);
76
+ expect(result.installedPremiumSpecialists).toEqual([]);
77
+ expect(result.registeredAgentTypes).toEqual(["plugin:admin:librarian", "plugin:admin:project-manager"]);
78
+ expect(result.brand.productName).toBe("Maxy");
79
+ expect(result.brand.shipsPremiumBundles).toEqual([]);
80
+ expect(result.capturedAt).toBe("2026-05-31T07:00:00Z");
81
+ });
82
+ it("returns Real Agent install set including premium specialists from each shipped bundle", async () => {
83
+ const { input } = seedTree(root, {
84
+ accountId: "acct-ra",
85
+ enabledPlugins: ["loop", "property-data", "estate-sales"],
86
+ tier: "agency",
87
+ productName: "Real Agent",
88
+ shipsPremiumBundles: ["real-agent"],
89
+ platformSpecialists: ["coding-assistant", "database-operator"],
90
+ premiumSpecialists: {
91
+ "real-agent": ["negotiator", "valuer", "compliance", "listing-curator"],
92
+ },
93
+ registeredAgents: ["plugin:real-agent:negotiator"],
94
+ });
95
+ const result = await resolveCapabilitiesHere(input, "2026-05-31T07:00:00Z");
96
+ expect(result.brand.productName).toBe("Real Agent");
97
+ expect(result.brand.shipsPremiumBundles).toEqual(["real-agent"]);
98
+ expect(result.installedPlatformSpecialists).toEqual(["coding-assistant", "database-operator"]);
99
+ expect(result.installedPremiumSpecialists).toEqual([
100
+ { bundle: "real-agent", name: "compliance" },
101
+ { bundle: "real-agent", name: "listing-curator" },
102
+ { bundle: "real-agent", name: "negotiator" },
103
+ { bundle: "real-agent", name: "valuer" },
104
+ ]);
105
+ expect(result.registeredAgentTypes).toEqual(["plugin:real-agent:negotiator"]);
106
+ });
107
+ it("handles missing fields and missing directories gracefully", async () => {
108
+ const accountConfigPath = join(root, "account.json");
109
+ const brandConfigPath = join(root, "brand.json");
110
+ writeFileSync(accountConfigPath, JSON.stringify({ accountId: "x" }));
111
+ writeFileSync(brandConfigPath, JSON.stringify({}));
112
+ const result = await resolveCapabilitiesHere({
113
+ accountId: "x",
114
+ accountConfigPath,
115
+ brandConfigPath,
116
+ platformRoot: join(root, "absent-platform"),
117
+ premiumPluginsRoot: join(root, "absent-premium"),
118
+ claudeConfigDir: join(root, "absent-claude"),
119
+ }, "2026-05-31T07:00:00Z");
120
+ expect(result.tier).toBeNull();
121
+ expect(result.adminModel).toBeNull();
122
+ expect(result.publicModel).toBeNull();
123
+ expect(result.enabledPlugins).toEqual([]);
124
+ expect(result.installedPlatformSpecialists).toEqual([]);
125
+ expect(result.installedPremiumSpecialists).toEqual([]);
126
+ expect(result.registeredAgentTypes).toEqual([]);
127
+ expect(result.brand.productName).toBeNull();
128
+ });
129
+ });
130
+ //# sourceMappingURL=capabilities-here.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities-here.test.js","sourceRoot":"","sources":["../../src/__tests__/capabilities-here.test.ts"],"names":[],"mappings":"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"}