@rubytech/create-maxy 1.0.473 → 1.0.475

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 (30) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +2 -2
  3. package/payload/platform/plugins/docs/references/migration-guide.md +29 -21
  4. package/payload/platform/plugins/memory/PLUGIN.md +6 -0
  5. package/payload/platform/plugins/memory/mcp/dist/index.js +58 -22
  6. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  7. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts +1 -0
  8. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -1
  9. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +35 -23
  10. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -1
  11. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +5 -0
  12. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  13. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +40 -7
  14. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  15. package/payload/platform/scripts/migrate-import.sh +17 -11
  16. package/payload/platform/scripts/seed-neo4j.sh +2 -55
  17. package/payload/server/public/assets/ChatInput-sDYraTun.css +1 -0
  18. package/payload/server/public/assets/{admin-BEbxw46k.js → admin-DpmnCxNk.js} +60 -60
  19. package/payload/server/public/assets/public-BBxDqQvQ.js +5 -0
  20. package/payload/server/public/index.html +3 -3
  21. package/payload/server/public/public.html +3 -3
  22. package/payload/server/server.js +108 -104
  23. package/payload/platform/plugins/admin/hooks/agent-creation-approval.sh +0 -161
  24. package/payload/platform/plugins/admin/hooks/agent-creation-gate.sh +0 -317
  25. package/payload/platform/plugins/admin/hooks/agent-creation-post.sh +0 -165
  26. package/payload/platform/plugins/admin/hooks/session-start.sh +0 -104
  27. package/payload/platform/plugins/admin/hooks/test-agent-creation-gate.sh +0 -926
  28. package/payload/server/public/assets/ChatInput-BEwQxFL9.css +0 -1
  29. package/payload/server/public/assets/public-OdyNuhVE.js +0 -5
  30. /package/payload/server/public/assets/{ChatInput-Dnp1FLis.js → ChatInput-DZ0j0Gdp.js} +0 -0
@@ -1,926 +0,0 @@
1
- #!/usr/bin/env bash
2
- # End-to-end test for the agent creation gate lifecycle.
3
- #
4
- # Validates that the hook scripts correctly:
5
- # - Block writes to agent files without a state file
6
- # - Block Bash writes to agent files without a state file
7
- # - Exempt admin agent files
8
- # - Create state on mcp__admin__plugin-read for public-agent-manager
9
- # - Do NOT create state for other plugin-read calls
10
- # - Block writes when gates are pending
11
- # - Advance gates on user approval (_componentDone via UserPromptSubmit)
12
- # - Allow writes after gates pass
13
- # - Allow Bash writes after gates pass
14
- # - Allow all gated writes when all gates pass
15
- # - Clean up state immediately when all files exist on disk (PostToolUse)
16
- # - Recover all-gates-true state on session start when files not yet written
17
- # - Detect and clean up truly completed state on session start (all gates true + files on disk)
18
- # - Ignore non-document-editor/form components
19
- # - Ignore _componentDone for admin agent
20
- # - Handle gate advancement idempotently
21
- # - render-component does NOT advance gates (removed in Task 148)
22
- #
23
- # Usage: bash test-agent-creation-gate.sh
24
- # Exit 0 = all tests pass, exit 1 = failure
25
- #
26
- # No Claude Code runtime needed — tests the hook scripts directly
27
- # by piping JSON input and checking exit codes / state files.
28
-
29
- set -euo pipefail
30
-
31
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
32
- TMPDIR_ROOT=$(mktemp -d)
33
- STATE_FILE="$TMPDIR_ROOT/state.json"
34
- ACCOUNT_DIR="$TMPDIR_ROOT/account"
35
- GATE_LOG="$TMPDIR_ROOT/gate.log"
36
-
37
- # Create mock account structure
38
- mkdir -p "$ACCOUNT_DIR/agents/test-agent"
39
- mkdir -p "$ACCOUNT_DIR/agents/admin"
40
- mkdir -p "$ACCOUNT_DIR/.claude"
41
-
42
- export AGENT_CREATE_STATE_FILE="$STATE_FILE"
43
- export ACCOUNT_DIR="$ACCOUNT_DIR"
44
- export GATE_LOG_FILE="$GATE_LOG"
45
-
46
- PASS=0
47
- FAIL=0
48
-
49
- pass() {
50
- PASS=$((PASS + 1))
51
- echo " PASS: $1"
52
- }
53
-
54
- fail() {
55
- FAIL=$((FAIL + 1))
56
- echo " FAIL: $1" >&2
57
- }
58
-
59
- cleanup() {
60
- rm -rf "$TMPDIR_ROOT"
61
- }
62
- trap cleanup EXIT
63
-
64
- echo "=== Agent Creation Gate — Lifecycle Test ==="
65
- echo ""
66
-
67
- # ---------------------------------------------------------------------------
68
- # Test 1: Write blocked without state file
69
- # ---------------------------------------------------------------------------
70
- echo "Test 1: Write blocked without state file"
71
- rm -f "$STATE_FILE"
72
-
73
- EXIT_CODE=0
74
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md","content":"test"}}' \
75
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
76
-
77
- if [ "$EXIT_CODE" -eq 2 ]; then
78
- pass "Write to agents/test-agent/SOUL.md blocked (exit 2)"
79
- else
80
- fail "Write to agents/test-agent/SOUL.md should exit 2, got $EXIT_CODE"
81
- fi
82
-
83
- # ---------------------------------------------------------------------------
84
- # Test 2: Bash blocked without state file
85
- # ---------------------------------------------------------------------------
86
- echo "Test 2: Bash blocked without state file"
87
- rm -f "$STATE_FILE"
88
-
89
- EXIT_CODE=0
90
- echo '{"tool_name":"Bash","tool_input":{"command":"cat > '"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md << EOF\ntest\nEOF"}}' \
91
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
92
-
93
- if [ "$EXIT_CODE" -eq 2 ]; then
94
- pass "Bash write to agents/test-agent/SOUL.md blocked (exit 2)"
95
- else
96
- fail "Bash write to agents/test-agent/SOUL.md should exit 2, got $EXIT_CODE"
97
- fi
98
-
99
- # ---------------------------------------------------------------------------
100
- # Test 3: Admin agent exempt
101
- # ---------------------------------------------------------------------------
102
- echo "Test 3: Admin agent exempt"
103
- rm -f "$STATE_FILE"
104
-
105
- EXIT_CODE=0
106
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/admin/SOUL.md","content":"test"}}' \
107
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
108
-
109
- if [ "$EXIT_CODE" -eq 0 ]; then
110
- pass "Write to agents/admin/SOUL.md allowed (exit 0)"
111
- else
112
- fail "Write to agents/admin/SOUL.md should exit 0, got $EXIT_CODE"
113
- fi
114
-
115
- # ---------------------------------------------------------------------------
116
- # Test 4: Non-PAM plugin-read does NOT create state
117
- # ---------------------------------------------------------------------------
118
- echo "Test 4: Non-PAM plugin-read does NOT create state"
119
- rm -f "$STATE_FILE"
120
-
121
- echo '{"tool_name":"mcp__admin__plugin-read","tool_input":{"pluginName":"admin","file":"skills/skill-builder/SKILL.md"}}' \
122
- | bash "$SCRIPT_DIR/agent-creation-post.sh" 2>/dev/null || true
123
-
124
- if [ ! -f "$STATE_FILE" ]; then
125
- pass "plugin-read for skill-builder did not create state file"
126
- else
127
- fail "plugin-read for skill-builder should NOT create state file"
128
- fi
129
-
130
- # ---------------------------------------------------------------------------
131
- # Test 5: State file creation on plugin-read for public-agent-manager
132
- # ---------------------------------------------------------------------------
133
- echo "Test 5: State file creation on plugin-read for public-agent-manager"
134
- rm -f "$STATE_FILE"
135
-
136
- echo '{"tool_name":"mcp__admin__plugin-read","tool_input":{"pluginName":"admin","file":"skills/public-agent-manager/skill.md"}}' \
137
- | bash "$SCRIPT_DIR/agent-creation-post.sh" 2>/dev/null || true
138
-
139
- if [ -f "$STATE_FILE" ]; then
140
- # Verify all gates are false
141
- SOUL_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['soul'])" 2>/dev/null)
142
- KNOWLEDGE_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['knowledge'])" 2>/dev/null)
143
- CONFIG_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['config'])" 2>/dev/null)
144
-
145
- if [ "$SOUL_GATE" = "False" ] && [ "$KNOWLEDGE_GATE" = "False" ] && [ "$CONFIG_GATE" = "False" ]; then
146
- pass "State file created with all gates false"
147
- else
148
- fail "State file gates should all be False, got soul=$SOUL_GATE knowledge=$KNOWLEDGE_GATE config=$CONFIG_GATE"
149
- fi
150
- else
151
- fail "State file not created after plugin-read for public-agent-manager"
152
- fi
153
-
154
- # ---------------------------------------------------------------------------
155
- # Test 6: Write blocked with pending gate
156
- # ---------------------------------------------------------------------------
157
- echo "Test 6: Write blocked with pending gate (soul=false)"
158
-
159
- EXIT_CODE=0
160
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md","content":"test"}}' \
161
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
162
-
163
- if [ "$EXIT_CODE" -eq 2 ]; then
164
- pass "Write blocked when soul gate is false (exit 2)"
165
- else
166
- fail "Write should be blocked when soul gate is false, got exit $EXIT_CODE"
167
- fi
168
-
169
- # ---------------------------------------------------------------------------
170
- # Test 7: Gate advancement — soul (user approval via _componentDone)
171
- # ---------------------------------------------------------------------------
172
- echo "Test 7: Gate advancement on user approval (soul)"
173
-
174
- python3 -c "
175
- import json
176
- payload = json.dumps({'action': 'approve', 'content': 'personality content', 'filePath': '$ACCOUNT_DIR/agents/test-agent/SOUL.md'})
177
- wrapper = json.dumps({'_componentDone': True, 'component': 'document-editor', 'payload': payload})
178
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
179
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
180
-
181
- SOUL_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['soul'])" 2>/dev/null)
182
- if [ "$SOUL_GATE" = "True" ]; then
183
- pass "Soul gate advanced to true"
184
- else
185
- fail "Soul gate should be True after user approval, got $SOUL_GATE"
186
- fi
187
-
188
- # ---------------------------------------------------------------------------
189
- # Test 8: Write allowed after gate passes
190
- # ---------------------------------------------------------------------------
191
- echo "Test 8: Write allowed after soul gate passes"
192
-
193
- EXIT_CODE=0
194
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md","content":"test"}}' \
195
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
196
-
197
- if [ "$EXIT_CODE" -eq 0 ]; then
198
- pass "Write to SOUL.md allowed after soul gate passes (exit 0)"
199
- else
200
- fail "Write to SOUL.md should be allowed after soul gate passes, got exit $EXIT_CODE"
201
- fi
202
-
203
- # ---------------------------------------------------------------------------
204
- # Test 9: Bash allowed after gate passes
205
- # ---------------------------------------------------------------------------
206
- echo "Test 9: Bash write allowed after soul gate passes"
207
-
208
- EXIT_CODE=0
209
- echo '{"tool_name":"Bash","tool_input":{"command":"cat > '"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md << EOF\ntest\nEOF"}}' \
210
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
211
-
212
- if [ "$EXIT_CODE" -eq 0 ]; then
213
- pass "Bash write to SOUL.md allowed after soul gate passes (exit 0)"
214
- else
215
- fail "Bash write to SOUL.md should be allowed after soul gate passes, got exit $EXIT_CODE"
216
- fi
217
-
218
- # ---------------------------------------------------------------------------
219
- # Test 10: All gates advanced — multiple writes succeed
220
- # ---------------------------------------------------------------------------
221
- echo "Test 10: All gates advanced, multiple gated writes succeed"
222
-
223
- # Advance knowledge gate (user approval)
224
- python3 -c "
225
- import json
226
- payload = json.dumps({'action': 'approve', 'content': 'knowledge content', 'filePath': '$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md'})
227
- wrapper = json.dumps({'_componentDone': True, 'component': 'document-editor', 'payload': payload})
228
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
229
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
230
-
231
- # Advance config gate (form submission)
232
- python3 -c "
233
- import json
234
- payload = 'Form submitted: {\"liveMemory\": true, \"model\": \"claude-haiku-4-5-20251001\"}'
235
- wrapper = json.dumps({'_componentDone': True, 'component': 'form', 'payload': payload})
236
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
237
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
238
-
239
- # All gates should be true — state file persists (6h TTL handles cleanup)
240
- if [ -f "$STATE_FILE" ]; then
241
- ALL_TRUE=$(python3 -c "import json; g=json.load(open('$STATE_FILE'))['gates']; print(all(g.values()))" 2>/dev/null)
242
- if [ "$ALL_TRUE" = "True" ]; then
243
- # Write all three gated files — each should succeed
244
- ALL_WRITES_OK=true
245
- for FILE in SOUL.md KNOWLEDGE.md config.json; do
246
- EXIT_CODE=0
247
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/'"$FILE"'","content":"test"}}' \
248
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
249
- if [ "$EXIT_CODE" -ne 0 ]; then
250
- ALL_WRITES_OK=false
251
- fail "Write to $FILE blocked after all gates true (exit $EXIT_CODE)"
252
- fi
253
- done
254
- if [ "$ALL_WRITES_OK" = true ]; then
255
- pass "All three gated files writable when all gates true"
256
- fi
257
- else
258
- fail "All gates should be true after three approvals"
259
- fi
260
- else
261
- fail "State file should still exist after approval hook"
262
- fi
263
-
264
- # ---------------------------------------------------------------------------
265
- # Test 11: Non-agent file writes always allowed
266
- # ---------------------------------------------------------------------------
267
- echo "Test 11: Non-agent file writes always allowed"
268
- rm -f "$STATE_FILE"
269
-
270
- EXIT_CODE=0
271
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/some-other-file.md","content":"test"}}' \
272
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
273
-
274
- if [ "$EXIT_CODE" -eq 0 ]; then
275
- pass "Write to non-agent file allowed (exit 0)"
276
- else
277
- fail "Write to non-agent file should exit 0, got $EXIT_CODE"
278
- fi
279
-
280
- # ===========================================================================
281
- # Hardening Tests
282
- # ===========================================================================
283
-
284
- # ---------------------------------------------------------------------------
285
- # Test 12: Stale state rejected (started > 6 hours ago)
286
- # ---------------------------------------------------------------------------
287
- echo "Test 12: Stale state rejected (started > 6 hours ago)"
288
- rm -f "$STATE_FILE"
289
-
290
- python3 -c "
291
- import json
292
- from datetime import datetime, timezone, timedelta
293
- state = {
294
- 'slug': 'test-agent',
295
- 'started': (datetime.now(timezone.utc) - timedelta(hours=7)).isoformat(),
296
- 'gates': {'soul': False, 'knowledge': False, 'config': False}
297
- }
298
- with open('$STATE_FILE', 'w') as f:
299
- json.dump(state, f)
300
- "
301
-
302
- EXIT_CODE=0
303
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md","content":"test"}}' \
304
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
305
-
306
- if [ "$EXIT_CODE" -eq 2 ]; then
307
- if [ ! -f "$STATE_FILE" ]; then
308
- pass "Stale state rejected (exit 2) and state file deleted"
309
- else
310
- fail "Stale state rejected (exit 2) but state file not deleted"
311
- fi
312
- else
313
- fail "Stale state should exit 2, got $EXIT_CODE"
314
- fi
315
-
316
- # ---------------------------------------------------------------------------
317
- # Test 13: Malformed state rejected (missing gate keys)
318
- # ---------------------------------------------------------------------------
319
- echo "Test 13: Malformed state rejected (missing gate keys)"
320
- rm -f "$STATE_FILE"
321
-
322
- python3 -c "
323
- import json
324
- from datetime import datetime, timezone
325
- state = {
326
- 'slug': 'test-agent',
327
- 'started': datetime.now(timezone.utc).isoformat(),
328
- 'gates': {'soul': True}
329
- }
330
- with open('$STATE_FILE', 'w') as f:
331
- json.dump(state, f)
332
- "
333
-
334
- EXIT_CODE=0
335
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md","content":"test"}}' \
336
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
337
-
338
- if [ "$EXIT_CODE" -eq 2 ]; then
339
- if [ ! -f "$STATE_FILE" ]; then
340
- pass "Malformed state rejected (exit 2) and state file deleted"
341
- else
342
- fail "Malformed state rejected (exit 2) but state file not deleted"
343
- fi
344
- else
345
- fail "Malformed state should exit 2, got $EXIT_CODE"
346
- fi
347
-
348
- # ---------------------------------------------------------------------------
349
- # Test 14: Legacy state rejected (no 'started' field)
350
- # ---------------------------------------------------------------------------
351
- echo "Test 14: Legacy state rejected (no 'started' field)"
352
- rm -f "$STATE_FILE"
353
-
354
- echo '{"gates":{"soul":true,"knowledge":true,"config":true}}' > "$STATE_FILE"
355
-
356
- EXIT_CODE=0
357
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md","content":"test"}}' \
358
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
359
-
360
- if [ "$EXIT_CODE" -eq 2 ]; then
361
- if [ ! -f "$STATE_FILE" ]; then
362
- pass "Legacy state (no started) rejected (exit 2) and state file deleted"
363
- else
364
- fail "Legacy state rejected (exit 2) but state file not deleted"
365
- fi
366
- else
367
- fail "Legacy state should exit 2, got $EXIT_CODE"
368
- fi
369
-
370
- # ---------------------------------------------------------------------------
371
- # Test 15: Bash blocked from writing state file
372
- # ---------------------------------------------------------------------------
373
- echo "Test 15: Bash blocked from writing state file"
374
- rm -f "$STATE_FILE"
375
-
376
- EXIT_CODE=0
377
- echo '{"tool_name":"Bash","tool_input":{"command":"echo '\''{}'\'' > /tmp/maxy-agent-create-state.json"}}' \
378
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
379
-
380
- if [ "$EXIT_CODE" -eq 2 ]; then
381
- pass "Bash write to state file blocked (exit 2)"
382
- else
383
- fail "Bash write to state file should exit 2, got $EXIT_CODE"
384
- fi
385
-
386
- # ---------------------------------------------------------------------------
387
- # Test 16: Bash blocked from deleting state file
388
- # ---------------------------------------------------------------------------
389
- echo "Test 16: Bash blocked from deleting state file"
390
- rm -f "$STATE_FILE"
391
-
392
- EXIT_CODE=0
393
- echo '{"tool_name":"Bash","tool_input":{"command":"rm /tmp/maxy-agent-create-state.json"}}' \
394
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
395
-
396
- if [ "$EXIT_CODE" -eq 2 ]; then
397
- pass "Bash delete of state file blocked (exit 2)"
398
- else
399
- fail "Bash delete of state file should exit 2, got $EXIT_CODE"
400
- fi
401
-
402
- # ---------------------------------------------------------------------------
403
- # Test 17: All-gates-true — state file persists, all writes allowed
404
- # ---------------------------------------------------------------------------
405
- echo "Test 17: All-gates-true state file persists across multiple writes"
406
- rm -f "$STATE_FILE"
407
-
408
- python3 -c "
409
- import json
410
- from datetime import datetime, timezone
411
- state = {
412
- 'slug': 'test-agent',
413
- 'started': datetime.now(timezone.utc).isoformat(),
414
- 'gates': {'soul': True, 'knowledge': True, 'config': True}
415
- }
416
- with open('$STATE_FILE', 'w') as f:
417
- json.dump(state, f)
418
- "
419
-
420
- # Write SOUL.md — should succeed and state file should persist
421
- EXIT_CODE=0
422
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md","content":"test"}}' \
423
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
424
-
425
- if [ "$EXIT_CODE" -eq 0 ] && [ -f "$STATE_FILE" ]; then
426
- # Write config.json — should also succeed (regression: previously blocked)
427
- EXIT_CODE=0
428
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/config.json","content":"test"}}' \
429
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
430
- if [ "$EXIT_CODE" -eq 0 ]; then
431
- pass "All-gates-true: multiple writes allowed, state file persists (6h TTL cleanup)"
432
- else
433
- fail "All-gates-true: second write blocked (exit $EXIT_CODE) — state file deleted prematurely"
434
- fi
435
- else
436
- fail "All-gates-true: first write should exit 0 with state file persisting, got exit=$EXIT_CODE file_exists=$([ -f '$STATE_FILE' ] && echo yes || echo no)"
437
- fi
438
-
439
- # ---------------------------------------------------------------------------
440
- # Test 18: Session-start staleness check (stale durable not recovered)
441
- # ---------------------------------------------------------------------------
442
- echo "Test 18: Session-start staleness check (stale durable not recovered)"
443
- rm -f "$STATE_FILE"
444
- DURABLE_FILE="$ACCOUNT_DIR/.claude/agent-create-state.json"
445
-
446
- python3 -c "
447
- import json
448
- from datetime import datetime, timezone, timedelta
449
- state = {
450
- 'slug': 'test-agent',
451
- 'started': (datetime.now(timezone.utc) - timedelta(hours=7)).isoformat(),
452
- 'gates': {'soul': True, 'knowledge': False, 'config': False}
453
- }
454
- with open('$DURABLE_FILE', 'w') as f:
455
- json.dump(state, f)
456
- "
457
-
458
- bash "$SCRIPT_DIR/session-start.sh" "$ACCOUNT_DIR" admin 2>/dev/null || true
459
-
460
- if [ ! -f "$STATE_FILE" ]; then
461
- if [ ! -f "$DURABLE_FILE" ]; then
462
- pass "Stale durable state not recovered, both files deleted"
463
- else
464
- fail "Stale durable state not recovered but durable file not deleted"
465
- fi
466
- else
467
- fail "Stale durable state should NOT be recovered to volatile"
468
- fi
469
-
470
- # ---------------------------------------------------------------------------
471
- # Test 19: Write tool blocked from targeting state file
472
- # ---------------------------------------------------------------------------
473
- echo "Test 19: Write tool blocked from targeting state file"
474
- rm -f "$STATE_FILE"
475
-
476
- EXIT_CODE=0
477
- echo '{"tool_name":"Write","tool_input":{"file_path":"/tmp/maxy-agent-create-state.json","content":"{\"gates\":{\"soul\":true,\"knowledge\":true,\"config\":true}}"}}' \
478
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
479
-
480
- if [ "$EXIT_CODE" -eq 2 ]; then
481
- pass "Write to state file blocked (exit 2)"
482
- else
483
- fail "Write to state file should exit 2, got $EXIT_CODE"
484
- fi
485
-
486
- # ---------------------------------------------------------------------------
487
- # Test 20: Edit tool blocked from targeting durable state file
488
- # ---------------------------------------------------------------------------
489
- echo "Test 20: Edit tool blocked from targeting durable state file"
490
- rm -f "$STATE_FILE"
491
-
492
- EXIT_CODE=0
493
- echo '{"tool_name":"Edit","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/.claude/agent-create-state.json","old_string":"false","new_string":"true"}}' \
494
- | bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
495
-
496
- if [ "$EXIT_CODE" -eq 2 ]; then
497
- pass "Edit to durable state file blocked (exit 2)"
498
- else
499
- fail "Edit to durable state file should exit 2, got $EXIT_CODE"
500
- fi
501
-
502
- # ===========================================================================
503
- # Approval hook tests — _componentDone gate advancement edge cases
504
- # ===========================================================================
505
-
506
- # ---------------------------------------------------------------------------
507
- # Test 21: Non-document-editor/form component does NOT advance any gate
508
- # ---------------------------------------------------------------------------
509
- echo "Test 21: Non-document-editor/form component does NOT advance any gate"
510
- rm -f "$STATE_FILE"
511
-
512
- # Create fresh state file
513
- echo '{"tool_name":"mcp__admin__plugin-read","tool_input":{"pluginName":"admin","file":"skills/public-agent-manager/skill.md"}}' \
514
- | bash "$SCRIPT_DIR/agent-creation-post.sh" 2>/dev/null || true
515
-
516
- # Submit a single-select component
517
- python3 -c "
518
- import json
519
- wrapper = json.dumps({'_componentDone': True, 'component': 'single-select', 'payload': 'option-a'})
520
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
521
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
522
-
523
- CONFIG_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['config'])" 2>/dev/null)
524
- SOUL_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['soul'])" 2>/dev/null)
525
- if [ "$CONFIG_GATE" = "False" ] && [ "$SOUL_GATE" = "False" ]; then
526
- pass "Non-document-editor/form component did not advance any gate"
527
- else
528
- fail "Non-gated component should not advance gates, got soul=$SOUL_GATE config=$CONFIG_GATE"
529
- fi
530
-
531
- # ---------------------------------------------------------------------------
532
- # Test 22: Document-editor for admin slug does NOT advance gate
533
- # ---------------------------------------------------------------------------
534
- echo "Test 22: Document-editor approval for admin slug does NOT advance gate"
535
-
536
- python3 -c "
537
- import json
538
- payload = json.dumps({'action': 'approve', 'content': 'admin content', 'filePath': '$ACCOUNT_DIR/agents/admin/SOUL.md'})
539
- wrapper = json.dumps({'_componentDone': True, 'component': 'document-editor', 'payload': payload})
540
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
541
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
542
-
543
- SOUL_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['soul'])" 2>/dev/null)
544
- if [ "$SOUL_GATE" = "False" ]; then
545
- pass "Document-editor approval for admin slug did not advance soul gate"
546
- else
547
- fail "Document-editor approval for admin slug should not advance soul gate, got $SOUL_GATE"
548
- fi
549
-
550
- # ---------------------------------------------------------------------------
551
- # Test 23: Gate advancement is idempotent
552
- # ---------------------------------------------------------------------------
553
- echo "Test 23: Gate advancement is idempotent"
554
-
555
- # Advance soul gate
556
- python3 -c "
557
- import json
558
- payload = json.dumps({'action': 'approve', 'content': 'personality', 'filePath': '$ACCOUNT_DIR/agents/test-agent/SOUL.md'})
559
- wrapper = json.dumps({'_componentDone': True, 'component': 'document-editor', 'payload': payload})
560
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
561
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
562
-
563
- # Advance soul gate again (should be idempotent)
564
- python3 -c "
565
- import json
566
- payload = json.dumps({'action': 'approve', 'content': 'updated personality', 'filePath': '$ACCOUNT_DIR/agents/test-agent/SOUL.md'})
567
- wrapper = json.dumps({'_componentDone': True, 'component': 'document-editor', 'payload': payload})
568
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
569
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
570
-
571
- SOUL_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['soul'])" 2>/dev/null)
572
- if [ "$SOUL_GATE" = "True" ]; then
573
- pass "Gate advancement is idempotent (soul still true after double approval)"
574
- else
575
- fail "Gate advancement should be idempotent, got soul=$SOUL_GATE"
576
- fi
577
-
578
- # ---------------------------------------------------------------------------
579
- # Test 24: Approval without state file is a no-op
580
- # ---------------------------------------------------------------------------
581
- echo "Test 24: Approval without state file is a no-op"
582
- rm -f "$STATE_FILE"
583
-
584
- python3 -c "
585
- import json
586
- payload = json.dumps({'action': 'approve', 'content': 'test', 'filePath': '$ACCOUNT_DIR/agents/test-agent/SOUL.md'})
587
- wrapper = json.dumps({'_componentDone': True, 'component': 'document-editor', 'payload': payload})
588
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
589
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
590
-
591
- if [ ! -f "$STATE_FILE" ]; then
592
- pass "Approval without state file did not create state"
593
- else
594
- fail "Approval without state file should not create state"
595
- fi
596
-
597
- # ---------------------------------------------------------------------------
598
- # Test 25: render-component does NOT advance gates (removed in Task 148)
599
- # ---------------------------------------------------------------------------
600
- echo "Test 25: render-component does NOT advance gates (PostToolUse)"
601
- rm -f "$STATE_FILE"
602
-
603
- # Create fresh state
604
- echo '{"tool_name":"mcp__admin__plugin-read","tool_input":{"pluginName":"admin","file":"skills/public-agent-manager/skill.md"}}' \
605
- | bash "$SCRIPT_DIR/agent-creation-post.sh" 2>/dev/null || true
606
-
607
- # Pipe render-component to the post hook — should NOT advance
608
- echo '{"tool_name":"mcp__admin__render-component","tool_input":{"name":"document-editor","data":{"title":"SOUL.md","content":"test","filePath":"'"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md"}}}' \
609
- | bash "$SCRIPT_DIR/agent-creation-post.sh" 2>/dev/null || true
610
-
611
- SOUL_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['soul'])" 2>/dev/null)
612
- if [ "$SOUL_GATE" = "False" ]; then
613
- pass "render-component to PostToolUse does NOT advance gates"
614
- else
615
- fail "render-component should NOT advance gates (Task 148 removed this), got soul=$SOUL_GATE"
616
- fi
617
-
618
- # ---------------------------------------------------------------------------
619
- # Test 26: Gate advancement order independence (config → knowledge → soul)
620
- # ---------------------------------------------------------------------------
621
- echo "Test 26: Gate advancement in non-standard order (config → knowledge → soul)"
622
-
623
- # State file from test 25 still exists with all gates false
624
-
625
- # Advance config first (form submission)
626
- python3 -c "
627
- import json
628
- wrapper = json.dumps({'_componentDone': True, 'component': 'form', 'payload': 'Form submitted: {\"liveMemory\": true}'})
629
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
630
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
631
-
632
- CONFIG_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['config'])" 2>/dev/null)
633
- if [ "$CONFIG_GATE" != "True" ]; then
634
- fail "Config gate should be True, got $CONFIG_GATE"
635
- fi
636
-
637
- # Advance knowledge second
638
- python3 -c "
639
- import json
640
- payload = json.dumps({'action': 'approve', 'content': 'knowledge', 'filePath': '$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md'})
641
- wrapper = json.dumps({'_componentDone': True, 'component': 'document-editor', 'payload': payload})
642
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
643
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
644
-
645
- KNOWLEDGE_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['knowledge'])" 2>/dev/null)
646
- if [ "$KNOWLEDGE_GATE" != "True" ]; then
647
- fail "Knowledge gate should be True, got $KNOWLEDGE_GATE"
648
- fi
649
-
650
- # Advance soul last
651
- python3 -c "
652
- import json
653
- payload = json.dumps({'action': 'approve', 'content': 'personality', 'filePath': '$ACCOUNT_DIR/agents/test-agent/SOUL.md'})
654
- wrapper = json.dumps({'_componentDone': True, 'component': 'document-editor', 'payload': payload})
655
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
656
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
657
-
658
- # All gates true — state file persists (6h TTL handles cleanup)
659
- if [ -f "$STATE_FILE" ]; then
660
- ALL_TRUE=$(python3 -c "import json; g=json.load(open('$STATE_FILE'))['gates']; print(all(g.values()))" 2>/dev/null)
661
- if [ "$ALL_TRUE" = "True" ]; then
662
- pass "Gates advanced in non-standard order, all true"
663
- else
664
- fail "All gates should be true after three approvals in any order"
665
- fi
666
- else
667
- fail "State file should still exist (cleanup is PreToolUse's job, not approval hook)"
668
- fi
669
-
670
- # ---------------------------------------------------------------------------
671
- # Test 27: Diagnostics log file is written
672
- # ---------------------------------------------------------------------------
673
- echo "Test 27: Diagnostics log file is written"
674
-
675
- if [ -f "$GATE_LOG" ] && [ -s "$GATE_LOG" ]; then
676
- pass "Gate log file exists and contains entries"
677
- else
678
- fail "Gate log file should exist and contain diagnostic entries"
679
- fi
680
-
681
- # ---------------------------------------------------------------------------
682
- # Test 28: Non-JSON user prompt is a no-op
683
- # ---------------------------------------------------------------------------
684
- echo "Test 28: Non-JSON user prompt is a no-op"
685
- rm -f "$STATE_FILE"
686
-
687
- # Create state
688
- echo '{"tool_name":"mcp__admin__plugin-read","tool_input":{"pluginName":"admin","file":"skills/public-agent-manager/skill.md"}}' \
689
- | bash "$SCRIPT_DIR/agent-creation-post.sh" 2>/dev/null || true
690
-
691
- # Send a plain text prompt (no _componentDone wrapper)
692
- python3 -c "
693
- import json
694
- print(json.dumps({'prompt': 'I approve the SOUL.md file', 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
695
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
696
-
697
- SOUL_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['soul'])" 2>/dev/null)
698
- if [ "$SOUL_GATE" = "False" ]; then
699
- pass "Plain text prompt did not advance any gate"
700
- else
701
- fail "Plain text prompt should not advance gates, got soul=$SOUL_GATE"
702
- fi
703
-
704
- # ---------------------------------------------------------------------------
705
- # Test 29: _componentDone with missing component key is a no-op
706
- # ---------------------------------------------------------------------------
707
- echo "Test 29: _componentDone with missing component key is a no-op"
708
-
709
- python3 -c "
710
- import json
711
- wrapper = json.dumps({'_componentDone': True, 'payload': 'some content'})
712
- print(json.dumps({'prompt': wrapper, 'session_id': 'test', 'hook_event_name': 'UserPromptSubmit'}))
713
- " | bash "$SCRIPT_DIR/agent-creation-approval.sh" 2>/dev/null || true
714
-
715
- SOUL_GATE=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['gates']['soul'])" 2>/dev/null)
716
- if [ "$SOUL_GATE" = "False" ]; then
717
- pass "_componentDone without component key did not advance any gate"
718
- else
719
- fail "_componentDone without component key should not advance gates"
720
- fi
721
-
722
- rm -f "$STATE_FILE"
723
-
724
- # ---------------------------------------------------------------------------
725
- # Test 30: Session-start RECOVERS all-gates-true state when files are missing
726
- # ---------------------------------------------------------------------------
727
- echo "Test 30: Session-start recovers all-gates-true state when files are missing"
728
- rm -f "$STATE_FILE"
729
- rm -f "$ACCOUNT_DIR/agents/test-agent/SOUL.md" "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md" "$ACCOUNT_DIR/agents/test-agent/config.json" 2>/dev/null || true
730
- DURABLE_FILE="$ACCOUNT_DIR/.claude/agent-create-state.json"
731
-
732
- python3 -c "
733
- import json
734
- from datetime import datetime, timezone
735
- state = {
736
- 'slug': 'test-agent',
737
- 'started': datetime.now(timezone.utc).isoformat(),
738
- 'gates': {'soul': True, 'knowledge': True, 'config': True}
739
- }
740
- with open('$DURABLE_FILE', 'w') as f:
741
- json.dump(state, f)
742
- "
743
-
744
- bash "$SCRIPT_DIR/session-start.sh" "$ACCOUNT_DIR" admin 2>/dev/null || true
745
-
746
- if [ -f "$STATE_FILE" ] && [ -f "$DURABLE_FILE" ]; then
747
- pass "All-gates-true state recovered (files not yet written — writes still pending)"
748
- else
749
- fail "All-gates-true state should be recovered when gated files are missing from disk"
750
- fi
751
-
752
- rm -f "$STATE_FILE"
753
-
754
- # ---------------------------------------------------------------------------
755
- # Test 30a: Session-start does NOT recover truly completed state (all gates true + all files on disk)
756
- # ---------------------------------------------------------------------------
757
- echo "Test 30a: Session-start does NOT recover truly completed state"
758
-
759
- # Create all gated files on disk
760
- touch "$ACCOUNT_DIR/agents/test-agent/SOUL.md"
761
- touch "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md"
762
- touch "$ACCOUNT_DIR/agents/test-agent/config.json"
763
-
764
- python3 -c "
765
- import json
766
- from datetime import datetime, timezone
767
- state = {
768
- 'slug': 'test-agent',
769
- 'started': datetime.now(timezone.utc).isoformat(),
770
- 'gates': {'soul': True, 'knowledge': True, 'config': True}
771
- }
772
- with open('$DURABLE_FILE', 'w') as f:
773
- json.dump(state, f)
774
- "
775
-
776
- bash "$SCRIPT_DIR/session-start.sh" "$ACCOUNT_DIR" admin 2>/dev/null || true
777
-
778
- if [ ! -f "$STATE_FILE" ] && [ ! -f "$DURABLE_FILE" ]; then
779
- pass "Truly completed state (all gates true + all files on disk) deleted"
780
- else
781
- fail "Truly completed state should be deleted — both gates passed and files written"
782
- fi
783
-
784
- # Clean up gated files for subsequent tests
785
- rm -f "$ACCOUNT_DIR/agents/test-agent/SOUL.md" "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md" "$ACCOUNT_DIR/agents/test-agent/config.json"
786
-
787
- # ---------------------------------------------------------------------------
788
- # Test 31: PostToolUse cleans up state after last gated file is written
789
- # ---------------------------------------------------------------------------
790
- echo "Test 31: PostToolUse cleans up state after last gated file is written"
791
- rm -f "$STATE_FILE"
792
- DURABLE_FILE="$ACCOUNT_DIR/.claude/agent-create-state.json"
793
-
794
- # Create state with all gates true
795
- python3 -c "
796
- import json
797
- from datetime import datetime, timezone
798
- state = {
799
- 'slug': 'test-agent',
800
- 'started': datetime.now(timezone.utc).isoformat(),
801
- 'gates': {'soul': True, 'knowledge': True, 'config': True}
802
- }
803
- with open('$STATE_FILE', 'w') as f:
804
- json.dump(state, f)
805
- with open('$DURABLE_FILE', 'w') as f:
806
- json.dump(state, f)
807
- "
808
-
809
- # Create all three gated files on disk
810
- touch "$ACCOUNT_DIR/agents/test-agent/SOUL.md"
811
- touch "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md"
812
- touch "$ACCOUNT_DIR/agents/test-agent/config.json"
813
-
814
- # Simulate a Write PostToolUse for config.json (the last file)
815
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/config.json","content":"{}"}}' \
816
- | bash "$SCRIPT_DIR/agent-creation-post.sh" 2>/dev/null || true
817
-
818
- if [ ! -f "$STATE_FILE" ] && [ ! -f "$DURABLE_FILE" ]; then
819
- pass "State files cleaned up immediately after all gated files exist"
820
- else
821
- fail "State files should be deleted when all gates true and all files exist (volatile=$([ -f '$STATE_FILE' ] && echo yes || echo no) durable=$([ -f '$DURABLE_FILE' ] && echo yes || echo no))"
822
- fi
823
-
824
- # ---------------------------------------------------------------------------
825
- # Test 32: PostToolUse does NOT clean up when a gated file is missing
826
- # ---------------------------------------------------------------------------
827
- echo "Test 32: PostToolUse does NOT clean up when a gated file is missing"
828
- rm -f "$STATE_FILE"
829
- rm -f "$DURABLE_FILE"
830
-
831
- # Create state with all gates true
832
- python3 -c "
833
- import json
834
- from datetime import datetime, timezone
835
- state = {
836
- 'slug': 'test-agent',
837
- 'started': datetime.now(timezone.utc).isoformat(),
838
- 'gates': {'soul': True, 'knowledge': True, 'config': True}
839
- }
840
- with open('$STATE_FILE', 'w') as f:
841
- json.dump(state, f)
842
- with open('$DURABLE_FILE', 'w') as f:
843
- json.dump(state, f)
844
- "
845
-
846
- # Only two files on disk — KNOWLEDGE.md missing
847
- touch "$ACCOUNT_DIR/agents/test-agent/SOUL.md"
848
- rm -f "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md"
849
- touch "$ACCOUNT_DIR/agents/test-agent/config.json"
850
-
851
- # Simulate a Write PostToolUse for config.json
852
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/config.json","content":"{}"}}' \
853
- | bash "$SCRIPT_DIR/agent-creation-post.sh" 2>/dev/null || true
854
-
855
- if [ -f "$STATE_FILE" ] && [ -f "$DURABLE_FILE" ]; then
856
- pass "State files preserved when KNOWLEDGE.md is missing on disk"
857
- else
858
- fail "State files should NOT be deleted when a gated file is missing"
859
- fi
860
-
861
- # ---------------------------------------------------------------------------
862
- # Test 33: PostToolUse does NOT clean up when gates are not all true
863
- # ---------------------------------------------------------------------------
864
- echo "Test 33: PostToolUse does NOT clean up when gates are not all true"
865
- rm -f "$STATE_FILE"
866
- rm -f "$DURABLE_FILE"
867
-
868
- # Create state with one gate still false
869
- python3 -c "
870
- import json
871
- from datetime import datetime, timezone
872
- state = {
873
- 'slug': 'test-agent',
874
- 'started': datetime.now(timezone.utc).isoformat(),
875
- 'gates': {'soul': True, 'knowledge': False, 'config': True}
876
- }
877
- with open('$STATE_FILE', 'w') as f:
878
- json.dump(state, f)
879
- with open('$DURABLE_FILE', 'w') as f:
880
- json.dump(state, f)
881
- "
882
-
883
- # All three files on disk
884
- touch "$ACCOUNT_DIR/agents/test-agent/SOUL.md"
885
- touch "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md"
886
- touch "$ACCOUNT_DIR/agents/test-agent/config.json"
887
-
888
- # Simulate a Write PostToolUse for SOUL.md
889
- echo '{"tool_name":"Write","tool_input":{"file_path":"'"$ACCOUNT_DIR"'/agents/test-agent/SOUL.md","content":"personality"}}' \
890
- | bash "$SCRIPT_DIR/agent-creation-post.sh" 2>/dev/null || true
891
-
892
- if [ -f "$STATE_FILE" ] && [ -f "$DURABLE_FILE" ]; then
893
- pass "State files preserved when knowledge gate is still false"
894
- else
895
- fail "State files should NOT be deleted when not all gates are true"
896
- fi
897
-
898
- # Clean up agent files for other tests
899
- rm -f "$ACCOUNT_DIR/agents/test-agent/SOUL.md" "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md" "$ACCOUNT_DIR/agents/test-agent/config.json"
900
- rm -f "$STATE_FILE" "$DURABLE_FILE"
901
-
902
- # ---------------------------------------------------------------------------
903
- # Test 27: Gate hook fails explicitly when ACCOUNT_DIR is unset
904
- # ---------------------------------------------------------------------------
905
- echo "Test 27: Gate hook fails explicitly when ACCOUNT_DIR is unset"
906
-
907
- EXIT_CODE=0
908
- echo '{"tool_name":"Write","tool_input":{"file_path":"/some/path/agents/test-agent/SOUL.md","content":"test"}}' \
909
- | env -u ACCOUNT_DIR -u GATE_LOG_FILE bash "$SCRIPT_DIR/agent-creation-gate.sh" 2>/dev/null || EXIT_CODE=$?
910
-
911
- if [ "$EXIT_CODE" -eq 2 ]; then
912
- pass "Gate hook exits 2 when ACCOUNT_DIR is unset (no silent /tmp fallback)"
913
- else
914
- fail "Gate hook should exit 2 when ACCOUNT_DIR is unset, got $EXIT_CODE"
915
- fi
916
-
917
- # ---------------------------------------------------------------------------
918
- # Summary
919
- # ---------------------------------------------------------------------------
920
- echo ""
921
- echo "=== Results: $PASS passed, $FAIL failed ==="
922
-
923
- if [ "$FAIL" -gt 0 ]; then
924
- exit 1
925
- fi
926
- exit 0