@rubytech/create-realagent-code 0.1.254 → 0.1.255

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 (158) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/.docs/search-surface-contract.md +58 -0
  3. package/payload/platform/lib/embed-client/dist/index.d.ts +4 -0
  4. package/payload/platform/lib/embed-client/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/embed-client/dist/index.js +53 -0
  6. package/payload/platform/lib/embed-client/dist/index.js.map +1 -0
  7. package/payload/platform/lib/embed-client/src/index.ts +53 -0
  8. package/payload/platform/lib/embed-client/tsconfig.json +8 -0
  9. package/payload/platform/lib/graph-search/dist/index.d.ts +27 -6
  10. package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
  11. package/payload/platform/lib/graph-search/dist/index.js +19 -1
  12. package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
  13. package/payload/platform/lib/graph-search/src/index.ts +28 -6
  14. package/payload/platform/lib/graph-write/dist/index.d.ts +25 -0
  15. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  16. package/payload/platform/lib/graph-write/dist/index.js +78 -2
  17. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  18. package/payload/platform/lib/graph-write/src/index.ts +96 -1
  19. package/payload/platform/package.json +2 -2
  20. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  21. package/payload/platform/plugins/admin/PLUGIN.md +3 -3
  22. package/payload/platform/plugins/admin/hooks/__tests__/{session-end-retrospective.test.sh → insight.test.sh} +152 -153
  23. package/payload/platform/plugins/admin/hooks/insight.sh +219 -0
  24. package/payload/platform/plugins/admin/hooks/lib/admin-graph-pass-common.sh +5 -5
  25. package/payload/platform/plugins/admin/mcp/dist/index.js +33 -19
  26. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  27. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +23 -23
  28. package/payload/platform/plugins/docs/references/graph.md +2 -0
  29. package/payload/platform/plugins/docs/references/internals.md +12 -1
  30. package/payload/platform/plugins/docs/references/neo4j.md +2 -2
  31. package/payload/platform/plugins/docs/references/platform.md +1 -1
  32. package/payload/platform/plugins/docs/references/session-retrospective.md +5 -18
  33. package/payload/platform/plugins/email/PLUGIN.md +2 -2
  34. package/payload/platform/plugins/email/mcp/dist/index.js +8 -0
  35. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  36. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts +19 -0
  37. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts.map +1 -0
  38. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js +64 -0
  39. package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js.map +1 -0
  40. package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts +4 -0
  41. package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts.map +1 -1
  42. package/payload/platform/plugins/email/mcp/dist/lib/smtp.js +1 -0
  43. package/payload/platform/plugins/email/mcp/dist/lib/smtp.js.map +1 -1
  44. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts +1 -0
  45. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -1
  46. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +6 -1
  47. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -1
  48. package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts +1 -0
  49. package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts.map +1 -1
  50. package/payload/platform/plugins/email/mcp/dist/tools/email-send.js +7 -1
  51. package/payload/platform/plugins/email/mcp/dist/tools/email-send.js.map +1 -1
  52. package/payload/platform/plugins/email/references/email-reference.md +4 -0
  53. package/payload/platform/plugins/memory/PLUGIN.md +1 -2
  54. package/payload/platform/plugins/memory/mcp/dist/index.js +5 -43
  55. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  56. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/document-sectioniser.test.d.ts +2 -0
  57. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/document-sectioniser.test.d.ts.map +1 -0
  58. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/document-sectioniser.test.js +41 -0
  59. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/document-sectioniser.test.js.map +1 -0
  60. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-embed-net.test.d.ts +2 -0
  61. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-embed-net.test.d.ts.map +1 -0
  62. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-embed-net.test.js +90 -0
  63. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-embed-net.test.js.map +1 -0
  64. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/vector-indexed-labels-drift.test.d.ts +2 -0
  65. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/vector-indexed-labels-drift.test.d.ts.map +1 -0
  66. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/vector-indexed-labels-drift.test.js +27 -0
  67. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/vector-indexed-labels-drift.test.js.map +1 -0
  68. package/payload/platform/plugins/memory/mcp/dist/lib/document-sectioniser.d.ts +10 -0
  69. package/payload/platform/plugins/memory/mcp/dist/lib/document-sectioniser.d.ts.map +1 -0
  70. package/payload/platform/plugins/memory/mcp/dist/lib/document-sectioniser.js +47 -0
  71. package/payload/platform/plugins/memory/mcp/dist/lib/document-sectioniser.js.map +1 -0
  72. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts +1 -2
  73. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts.map +1 -1
  74. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js +5 -28
  75. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js.map +1 -1
  76. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/build-text-representation-bound.test.d.ts +2 -0
  77. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/build-text-representation-bound.test.d.ts.map +1 -0
  78. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/build-text-representation-bound.test.js +20 -0
  79. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/build-text-representation-bound.test.js.map +1 -0
  80. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-preference-embed.test.d.ts +2 -0
  81. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-preference-embed.test.d.ts.map +1 -0
  82. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-preference-embed.test.js +67 -0
  83. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-preference-embed.test.js.map +1 -0
  84. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.d.ts +2 -0
  85. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.d.ts.map +1 -0
  86. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.js +34 -0
  87. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.js.map +1 -0
  88. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-section-writer.test.d.ts +2 -0
  89. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-section-writer.test.d.ts.map +1 -0
  90. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-section-writer.test.js +61 -0
  91. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-section-writer.test.js.map +1 -0
  92. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +23 -1
  93. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -1
  94. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex.test.d.ts +2 -0
  95. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex.test.d.ts.map +1 -0
  96. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex.test.js +87 -0
  97. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex.test.js.map +1 -0
  98. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js +3 -0
  99. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js.map +1 -1
  100. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-threshold.test.d.ts +2 -0
  101. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-threshold.test.d.ts.map +1 -0
  102. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-threshold.test.js +34 -0
  103. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-threshold.test.js.map +1 -0
  104. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
  105. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +19 -4
  106. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
  107. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +33 -6
  108. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
  109. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +280 -10
  110. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
  111. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -1
  112. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +76 -37
  113. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -1
  114. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
  115. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +11 -2
  116. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
  117. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +3 -3
  118. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +2 -2
  119. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  120. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +10 -2
  121. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  122. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +6 -3
  123. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  124. package/payload/platform/plugins/memory/mcp/vitest.config.ts +5 -0
  125. package/payload/platform/plugins/memory/references/schema-base.md +1 -1
  126. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.d.ts.map +1 -1
  127. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js +15 -0
  128. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js.map +1 -1
  129. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js +4 -0
  130. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js.map +1 -1
  131. package/payload/platform/scripts/identity-forbidden-token-check.mjs +0 -1
  132. package/payload/platform/scripts/setup-account.sh +2 -8
  133. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts +23 -0
  134. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts.map +1 -0
  135. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js +29 -0
  136. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js.map +1 -0
  137. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  138. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +0 -1
  139. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  140. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  141. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +8 -1
  142. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  143. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts +13 -1
  144. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
  145. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +26 -2
  146. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
  147. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +1 -1
  148. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  149. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +12 -3
  150. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
  151. package/payload/platform/services/claude-session-manager/dist/system-prompt.js +3 -2
  152. package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
  153. package/payload/platform/templates/agents/admin/IDENTITY.md +2 -2
  154. package/payload/platform/templates/specialists/agents/database-operator.md +2 -6
  155. package/payload/server/{chunk-M6A6EZD4.js → chunk-76HRO7NX.js} +16 -2
  156. package/payload/server/maxy-edge.js +1 -1
  157. package/payload/server/server.js +473 -28
  158. package/payload/platform/plugins/admin/hooks/session-end-retrospective.sh +0 -214
@@ -1,45 +1,40 @@
1
1
  #!/usr/bin/env bash
2
- # Task 282session-end retrospective Stop hook test suite.
2
+ # Task 634on-demand `/insight` UserPromptSubmit hook test suite.
3
3
  #
4
4
  # Listener-mock pattern: a local Python HTTP server stands in for
5
5
  # /api/admin/log-ingest and records every POST so the assertions can read
6
6
  # every log line the hook emitted.
7
7
  #
8
- # Contract under test:
9
- # - All log emissions go through POST /api/admin/log-ingest. Hook stderr
10
- # stays silent on every path EXCEPT the gate-blocked path, which writes
11
- # the three-pass retrospective instruction block to stderr (Stop-hook
12
- # contractthat is how the agent receives the instruction).
13
- # - Every gated-off path emits one `trigger-skipped reason=<r>` line and
14
- # exits 0:
8
+ # Contract under test (run-and-continue, no Stop gate):
9
+ # - Event is UserPromptSubmit. Trigger detection reads the `prompt` field
10
+ # of the stdin envelope, NOT the transcript. The token `/insight` is
11
+ # matched case-insensitively, as the whole message (after strip) OR as a
12
+ # standalone line never embedded in prose.
13
+ # - On match: the four-pass instruction is printed to STDOUT (UserPromptSubmit
14
+ # stdout is injected as turn context), `trigger sessionId=<id> token=/insight`
15
+ # is emitted via log-ingest, exit 0. No stderr instruction, no exit 2.
16
+ # - Every gated-off / non-match path emits one `trigger-skipped reason=<r>`
17
+ # line and exits 0:
15
18
  # role-not-admin | is-specialist | empty-stdin | missing-transcript |
16
- # no-intent-match
17
- # - End-intent triggers: `/end`, `/archive`, `end session`,
18
- # `archive this session`, applied case-insensitively to the latest
19
- # real-user message. `tool_result`-only user records are NOT the
20
- # latest user message. Embedded prose ("I'll end session by 5pm")
21
- # does NOT trigger.
22
- # - When the intent matches and the sentinel `tool_use` of
23
- # `session-retrospective-mark-complete` is absent from the JSONL:
24
- # exit 2, stderr carries the instruction block,
25
- # `gate-blocked sessionId=<id> reason=sentinel-absent` emitted.
26
- # - When the intent matches and the sentinel `tool_use` is present:
27
- # exit 0, `gate-released sessionId=<id>` emitted.
28
- # - Re-entry: a second Stop fired after the sentinel call still releases
29
- # (the sentinel-grep is the re-entry guard).
30
- # - Per-session scoping: a sentinel in a DIFFERENT session's JSONL does
31
- # NOT release the active gate.
19
+ # no-insight-token
20
+ # - Standing reconciliation: on every invocation the hook scans the
21
+ # transcript (`transcript_path`) for the latest real-user turn whose text
22
+ # matches `/insight`; if no `session-retrospective-mark-complete` tool_use
23
+ # appears after that turn, it emits
24
+ # `prior-incomplete sessionId=<id> triggered-turn=<idx>`. A sentinel after
25
+ # the latest `/insight` turn suppresses the emit.
26
+ # - All log emissions go through POST /api/admin/log-ingest. The only stdout
27
+ # writer is the instruction block on the trigger path.
32
28
 
33
29
  set -u
34
30
 
35
- HOOK="$(cd "$(dirname "$0")/.." && pwd)/session-end-retrospective.sh"
31
+ HOOK="$(cd "$(dirname "$0")/.." && pwd)/insight.sh"
36
32
  if [[ ! -x "$HOOK" ]]; then
37
33
  echo "FAIL: $HOOK not executable" >&2
38
34
  exit 1
39
35
  fi
40
36
 
41
37
  OP_ID='aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb'
42
- OTHER_ID='99999999-7777-6666-5555-444444444444'
43
38
 
44
39
  TMPFILES=()
45
40
  LISTENER_PIDS=()
@@ -120,14 +115,11 @@ for raw in sys.stdin:
120
115
  ' || true
121
116
  }
122
117
 
123
- # Build a JSONL transcript from positional jq-style triples. Each triple is
124
- # role|text|extra where extra is one of:
125
- # "" → plain user/assistant text record
126
- # "tool_use:<NAME>" → assistant record carrying a
127
- # tool_use block of that name
128
- # "tool_result" → user record carrying only a
129
- # tool_result block (not a real
130
- # user turn)
118
+ # Build a JSONL transcript from positional triples. Each triple is
119
+ # role|text|extra where extra is one of:
120
+ # "" → plain user/assistant text record
121
+ # "tool_use:<NAME>" → assistant record carrying a tool_use block
122
+ # "tool_result" → user record carrying only a tool_result block
131
123
  write_transcript() {
132
124
  local out="$1"; shift
133
125
  : > "$out"
@@ -138,7 +130,7 @@ spec = sys.argv[2:]
138
130
  with open(out, "w", encoding="utf-8") as f:
139
131
  for entry in spec:
140
132
  role, text, extra = entry.split("|", 2)
141
- rec = {"type": role, "timestamp": "2026-05-24T10:00:00.000Z"}
133
+ rec = {"type": role, "timestamp": "2026-06-05T10:00:00.000Z"}
142
134
  if role == "user":
143
135
  if extra == "tool_result":
144
136
  rec["message"] = {
@@ -172,24 +164,33 @@ with open(out, "w", encoding="utf-8") as f:
172
164
  PY
173
165
  }
174
166
 
167
+ # UserPromptSubmit envelope: session_id, transcript_path, prompt, hook_event_name.
175
168
  envelope_for() {
176
169
  python3 -c '
177
170
  import json, sys
178
- print(json.dumps({"session_id": sys.argv[1], "transcript_path": sys.argv[2]}))
179
- ' "$1" "$2"
171
+ print(json.dumps({
172
+ "session_id": sys.argv[1],
173
+ "transcript_path": sys.argv[2],
174
+ "prompt": sys.argv[3],
175
+ "hook_event_name": "UserPromptSubmit",
176
+ }))
177
+ ' "$1" "$2" "$3"
180
178
  }
181
179
 
180
+ # A transcript with no prior /insight turn — used whenever the test is about
181
+ # trigger detection and reconciliation must stay silent.
182
+ NEUTRAL_TRANSCRIPT=$(mktemp); TMPFILES+=("$NEUTRAL_TRANSCRIPT")
183
+
182
184
  # ---------------------------------------------------------------------------
183
185
  # Case 1: role != admin → trigger-skipped reason=role-not-admin
184
186
  # ---------------------------------------------------------------------------
185
187
  start_listener
186
- TRANSCRIPT_PUBLIC=$(mktemp); TMPFILES+=("$TRANSCRIPT_PUBLIC")
187
- write_transcript "$TRANSCRIPT_PUBLIC" "user|/end|"
188
- ENV_PUBLIC=$(envelope_for "$OP_ID" "$TRANSCRIPT_PUBLIC")
188
+ write_transcript "$NEUTRAL_TRANSCRIPT" "user|hello|" "assistant|hi|"
189
+ ENV_INSIGHT=$(envelope_for "$OP_ID" "$NEUTRAL_TRANSCRIPT" "/insight")
189
190
  : > "$REQ_LOG"
190
- run_hook "public" "" "$ENV_PUBLIC"
191
+ run_hook "public" "" "$ENV_INSIGHT"
191
192
  [[ "$HOOK_RC" -eq 0 ]] || fail "case-1 rc=$HOOK_RC"
192
- [[ -z "$HOOK_STDERR" ]] || fail "case-1 stderr must be empty, got: $HOOK_STDERR"
193
+ [[ -z "$HOOK_STDOUT" ]] || fail "case-1 stdout must be empty, got: $HOOK_STDOUT"
193
194
  if ingest_lines | grep -qE '^trigger-skipped sessionId=.* reason=role-not-admin$'; then
194
195
  pass "case-1 role=public → trigger-skipped reason=role-not-admin"
195
196
  else
@@ -200,9 +201,9 @@ fi
200
201
  # Case 2: MAXY_SPECIALIST set → trigger-skipped is-specialist
201
202
  # ---------------------------------------------------------------------------
202
203
  : > "$REQ_LOG"
203
- run_hook "admin" "database-operator" "$ENV_PUBLIC"
204
+ run_hook "admin" "database-operator" "$ENV_INSIGHT"
204
205
  [[ "$HOOK_RC" -eq 0 ]] || fail "case-2 rc=$HOOK_RC"
205
- [[ -z "$HOOK_STDERR" ]] || fail "case-2 stderr must be empty, got: $HOOK_STDERR"
206
+ [[ -z "$HOOK_STDOUT" ]] || fail "case-2 stdout must be empty, got: $HOOK_STDOUT"
206
207
  if ingest_lines | grep -qE '^trigger-skipped sessionId=.* reason=is-specialist specialist=database-operator$'; then
207
208
  pass "case-2 specialist=database-operator → trigger-skipped is-specialist"
208
209
  else
@@ -225,7 +226,7 @@ fi
225
226
  # Case 4: missing transcript_path → trigger-skipped missing-transcript
226
227
  # ---------------------------------------------------------------------------
227
228
  : > "$REQ_LOG"
228
- BAD_ENV=$(python3 -c 'import json,sys; print(json.dumps({"session_id": sys.argv[1]}))' "$OP_ID")
229
+ BAD_ENV=$(python3 -c 'import json,sys; print(json.dumps({"session_id": sys.argv[1], "prompt": "/insight"}))' "$OP_ID")
229
230
  run_hook "admin" "" "$BAD_ENV"
230
231
  [[ "$HOOK_RC" -eq 0 ]] || fail "case-4 rc=$HOOK_RC"
231
232
  if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=missing-transcript$"; then
@@ -235,162 +236,160 @@ else
235
236
  fi
236
237
 
237
238
  # ---------------------------------------------------------------------------
238
- # Case 5: no end-intent token → trigger-skipped reason=no-intent-match
239
+ # Case 5: prompt has no /insight token → trigger-skipped reason=no-insight-token
239
240
  # ---------------------------------------------------------------------------
240
- TRANSCRIPT_BORING=$(mktemp); TMPFILES+=("$TRANSCRIPT_BORING")
241
- write_transcript "$TRANSCRIPT_BORING" \
242
- "user|hello|" \
243
- "assistant|hi|" \
244
- "user|what's the weather|"
245
- ENV_BORING=$(envelope_for "$OP_ID" "$TRANSCRIPT_BORING")
241
+ ENV_BORING=$(envelope_for "$OP_ID" "$NEUTRAL_TRANSCRIPT" "what's the weather today")
246
242
  : > "$REQ_LOG"
247
243
  run_hook "admin" "" "$ENV_BORING"
248
244
  [[ "$HOOK_RC" -eq 0 ]] || fail "case-5 rc=$HOOK_RC"
249
- [[ -z "$HOOK_STDERR" ]] || fail "case-5 stderr must be empty"
250
- if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=no-intent-match$"; then
251
- pass "case-5 no intent token → trigger-skipped reason=no-intent-match"
245
+ [[ -z "$HOOK_STDOUT" ]] || fail "case-5 stdout must be empty"
246
+ if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=no-insight-token$"; then
247
+ pass "case-5 no token → trigger-skipped reason=no-insight-token"
252
248
  else
253
- fail "case-5 expected no-intent-match, got: $(ingest_lines)"
249
+ fail "case-5 expected no-insight-token, got: $(ingest_lines)"
254
250
  fi
255
251
 
256
252
  # ---------------------------------------------------------------------------
257
- # Case 6: each intent token triggers gate-blocked (sentinel absent)
253
+ # Case 6: prompt is exactly /insight instruction on stdout, trigger logged
258
254
  # ---------------------------------------------------------------------------
259
- for TOKEN in "/end" "/archive" "end session" "archive this session"; do
260
- T_FILE=$(mktemp); TMPFILES+=("$T_FILE")
261
- write_transcript "$T_FILE" "user|hi|" "assistant|hello|" "user|${TOKEN}|"
262
- ENV_BLOCK=$(envelope_for "$OP_ID" "$T_FILE")
263
- : > "$REQ_LOG"
264
- run_hook "admin" "" "$ENV_BLOCK"
265
- if [[ "$HOOK_RC" -ne 2 ]]; then
266
- fail "case-6:${TOKEN} expected rc=2, got $HOOK_RC"
267
- continue
268
- fi
269
- if ! ingest_lines | grep -qE "^trigger sessionId=${OP_ID} token=${TOKEN}$"; then
270
- fail "case-6:${TOKEN} missing trigger line, got: $(ingest_lines)"
271
- continue
272
- fi
273
- if ! ingest_lines | grep -qE "^gate-blocked sessionId=${OP_ID} reason=sentinel-absent$"; then
274
- fail "case-6:${TOKEN} missing gate-blocked line, got: $(ingest_lines)"
275
- continue
276
- fi
277
- if ! printf '%s' "$HOOK_STDERR" | grep -q "session-retrospective-mark-complete"; then
278
- fail "case-6:${TOKEN} instruction block missing sentinel tool name on stderr"
279
- continue
280
- fi
281
- if ! printf '%s' "$HOOK_STDERR" | grep -q "database-operator"; then
282
- fail "case-6:${TOKEN} instruction block missing database-operator reference"
283
- continue
284
- fi
285
- pass "case-6:${TOKEN} triggers gate-blocked exit 2 + instruction stderr"
286
- done
255
+ : > "$REQ_LOG"
256
+ run_hook "admin" "" "$ENV_INSIGHT"
257
+ [[ "$HOOK_RC" -eq 0 ]] || fail "case-6 expected rc=0, got $HOOK_RC"
258
+ [[ -z "$HOOK_STDERR" ]] || fail "case-6 stderr must be empty (no exit-2 instruction path), got: $HOOK_STDERR"
259
+ if ! printf '%s' "$HOOK_STDOUT" | grep -q "session-retrospective-mark-complete"; then
260
+ fail "case-6 instruction (stdout) missing sentinel tool name"
261
+ fi
262
+ if ! printf '%s' "$HOOK_STDOUT" | grep -q "database-operator"; then
263
+ fail "case-6 instruction (stdout) missing database-operator reference"
264
+ fi
265
+ if ! printf '%s' "$HOOK_STDOUT" | grep -qi "keep going\|continue"; then
266
+ fail "case-6 instruction (stdout) missing run-and-continue wording"
267
+ fi
268
+ if ingest_lines | grep -qE "^trigger sessionId=${OP_ID} token=/insight$"; then
269
+ pass "case-6 /insight whole-message instruction on stdout + trigger logged"
270
+ else
271
+ fail "case-6 expected trigger line, got: $(ingest_lines)"
272
+ fi
287
273
 
288
274
  # ---------------------------------------------------------------------------
289
- # Case 7: intent token + sentinel present gate-released exit 0
275
+ # Case 7: /insight as a standalone line among other lines → triggers
290
276
  # ---------------------------------------------------------------------------
291
- T_RELEASE=$(mktemp); TMPFILES+=("$T_RELEASE")
292
- write_transcript "$T_RELEASE" \
293
- "user|hi|" \
294
- "assistant|hello|" \
295
- "user|/end|" \
296
- "assistant|running retrospective|" \
297
- "assistant||tool_use:mcp__admin__session-retrospective-mark-complete"
298
- ENV_REL=$(envelope_for "$OP_ID" "$T_RELEASE")
277
+ ENV_LINE=$(envelope_for "$OP_ID" "$NEUTRAL_TRANSCRIPT" $'here is some context\n/insight\nthanks')
299
278
  : > "$REQ_LOG"
300
- run_hook "admin" "" "$ENV_REL"
301
- [[ "$HOOK_RC" -eq 0 ]] || fail "case-7 expected rc=0, got $HOOK_RC"
302
- [[ -z "$HOOK_STDERR" ]] || fail "case-7 stderr must be empty, got: $HOOK_STDERR"
303
- if ingest_lines | grep -qE "^gate-released sessionId=${OP_ID}$"; then
304
- pass "case-7 sentinel present → gate-released exit 0"
279
+ run_hook "admin" "" "$ENV_LINE"
280
+ [[ "$HOOK_RC" -eq 0 ]] || fail "case-7 rc=$HOOK_RC"
281
+ if ingest_lines | grep -qE "^trigger sessionId=${OP_ID} token=/insight$"; then
282
+ pass "case-7 /insight standalone line triggers"
305
283
  else
306
- fail "case-7 expected gate-released, got: $(ingest_lines)"
284
+ fail "case-7 expected trigger, got: $(ingest_lines)"
307
285
  fi
308
286
 
309
287
  # ---------------------------------------------------------------------------
310
- # Case 8: re-entryStop fires twice after sentinel call, both release
288
+ # Case 8: case-insensitive/INSIGHT triggers, logged as /insight
311
289
  # ---------------------------------------------------------------------------
290
+ ENV_CASE=$(envelope_for "$OP_ID" "$NEUTRAL_TRANSCRIPT" "/INSIGHT")
312
291
  : > "$REQ_LOG"
313
- run_hook "admin" "" "$ENV_REL"
314
- [[ "$HOOK_RC" -eq 0 ]] || fail "case-8 second-stop rc=$HOOK_RC"
315
- if ingest_lines | grep -qE "^gate-released sessionId=${OP_ID}$"; then
316
- pass "case-8 re-entry: second Stop after sentinel still releases"
292
+ run_hook "admin" "" "$ENV_CASE"
293
+ [[ "$HOOK_RC" -eq 0 ]] || fail "case-8 rc=$HOOK_RC"
294
+ if ingest_lines | grep -qE "^trigger sessionId=${OP_ID} token=/insight$"; then
295
+ pass "case-8 case-insensitive: /INSIGHT normalises to /insight"
317
296
  else
318
- fail "case-8 second Stop did not emit gate-released, got: $(ingest_lines)"
297
+ fail "case-8 expected token=/insight, got: $(ingest_lines)"
319
298
  fi
320
299
 
321
300
  # ---------------------------------------------------------------------------
322
- # Case 9: per-session scoping sentinel lives in a different session's
323
- # JSONL; active gate must NOT release
301
+ # Case 9: embedded prose does NOT trigger
324
302
  # ---------------------------------------------------------------------------
325
- T_OTHER=$(mktemp); TMPFILES+=("$T_OTHER")
326
- write_transcript "$T_OTHER" \
327
- "user|/end|" \
328
- "assistant||tool_use:mcp__admin__session-retrospective-mark-complete"
329
- # Active gate inspects only T_ACTIVE (no sentinel inside).
330
- T_ACTIVE=$(mktemp); TMPFILES+=("$T_ACTIVE")
331
- write_transcript "$T_ACTIVE" "user|/end|"
332
- ENV_ACTIVE=$(envelope_for "$OP_ID" "$T_ACTIVE")
303
+ ENV_PROSE=$(envelope_for "$OP_ID" "$NEUTRAL_TRANSCRIPT" "can you give me some /insight into the numbers")
304
+ : > "$REQ_LOG"
305
+ run_hook "admin" "" "$ENV_PROSE"
306
+ [[ "$HOOK_RC" -eq 0 ]] || fail "case-9 rc=$HOOK_RC"
307
+ [[ -z "$HOOK_STDOUT" ]] || fail "case-9 stdout must be empty (prose must not trigger)"
308
+ if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=no-insight-token$"; then
309
+ pass "case-9 embedded '/insight' prose does not trigger"
310
+ else
311
+ fail "case-9 expected no-insight-token, got: $(ingest_lines)"
312
+ fi
313
+
314
+ # ---------------------------------------------------------------------------
315
+ # Case 10: reconciliation — prior /insight turn with NO sentinel after it →
316
+ # prior-incomplete emitted (and current non-token prompt skips)
317
+ # ---------------------------------------------------------------------------
318
+ T_INCOMPLETE=$(mktemp); TMPFILES+=("$T_INCOMPLETE")
319
+ write_transcript "$T_INCOMPLETE" \
320
+ "user|hi|" \
321
+ "assistant|hello|" \
322
+ "user|/insight|" \
323
+ "assistant|here is a prose summary|"
324
+ ENV_RECON=$(envelope_for "$OP_ID" "$T_INCOMPLETE" "what's next")
333
325
  : > "$REQ_LOG"
334
- run_hook "admin" "" "$ENV_ACTIVE"
335
- [[ "$HOOK_RC" -eq 2 ]] || fail "case-9 expected rc=2 (other session's sentinel must not release), got $HOOK_RC"
336
- if ingest_lines | grep -qE "^gate-blocked sessionId=${OP_ID} reason=sentinel-absent$"; then
337
- pass "case-9 per-session scoping: sentinel in another transcript does not release"
326
+ run_hook "admin" "" "$ENV_RECON"
327
+ [[ "$HOOK_RC" -eq 0 ]] || fail "case-10 rc=$HOOK_RC"
328
+ if ! ingest_lines | grep -qE "^prior-incomplete sessionId=${OP_ID} triggered-turn=[0-9]+$"; then
329
+ fail "case-10 expected prior-incomplete, got: $(ingest_lines)"
330
+ elif ! ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=no-insight-token$"; then
331
+ fail "case-10 expected current prompt skip, got: $(ingest_lines)"
338
332
  else
339
- fail "case-9 expected gate-blocked, got: $(ingest_lines)"
333
+ pass "case-10 prior /insight without sentinel → prior-incomplete"
340
334
  fi
341
335
 
342
336
  # ---------------------------------------------------------------------------
343
- # Case 10: `tool_result`-only user record is NOT the latest user message;
344
- # the prior real user message wins.
337
+ # Case 11: reconciliation negative prior /insight turn WITH a sentinel
338
+ # tool_use after it no prior-incomplete
345
339
  # ---------------------------------------------------------------------------
346
- T_TR_ONLY=$(mktemp); TMPFILES+=("$T_TR_ONLY")
347
- write_transcript "$T_TR_ONLY" \
348
- "user|/end|" \
349
- "assistant||tool_use:Read" \
350
- "user|noise|tool_result"
351
- ENV_TR=$(envelope_for "$OP_ID" "$T_TR_ONLY")
340
+ T_COMPLETE=$(mktemp); TMPFILES+=("$T_COMPLETE")
341
+ write_transcript "$T_COMPLETE" \
342
+ "user|hi|" \
343
+ "user|/insight|" \
344
+ "assistant|running passes|" \
345
+ "assistant||tool_use:mcp__admin__session-retrospective-mark-complete"
346
+ ENV_DONE=$(envelope_for "$OP_ID" "$T_COMPLETE" "what's next")
352
347
  : > "$REQ_LOG"
353
- run_hook "admin" "" "$ENV_TR"
354
- [[ "$HOOK_RC" -eq 2 ]] || fail "case-10 expected rc=2 (tool_result must not mask /end), got $HOOK_RC"
355
- if ingest_lines | grep -qE "^gate-blocked sessionId=${OP_ID} reason=sentinel-absent$"; then
356
- pass "case-10 tool_result-only user record does not mask latest real user /end"
348
+ run_hook "admin" "" "$ENV_DONE"
349
+ [[ "$HOOK_RC" -eq 0 ]] || fail "case-11 rc=$HOOK_RC"
350
+ if ingest_lines | grep -qE "^prior-incomplete"; then
351
+ fail "case-11 sentinel present must suppress prior-incomplete, got: $(ingest_lines)"
357
352
  else
358
- fail "case-10 expected gate-blocked, got: $(ingest_lines)"
353
+ pass "case-11 prior /insight with sentinel → no prior-incomplete"
359
354
  fi
360
355
 
361
356
  # ---------------------------------------------------------------------------
362
- # Case 11: embedded prose does NOT trigger ("I'll end session by 5pm")
357
+ # Case 13: the just-submitted /insight turn is already in the transcript with
358
+ # no assistant response yet → must NOT self-report prior-incomplete.
359
+ # (Guards against the false positive if Claude Code writes the
360
+ # current prompt to the transcript before UserPromptSubmit fires.)
363
361
  # ---------------------------------------------------------------------------
364
- T_PROSE=$(mktemp); TMPFILES+=("$T_PROSE")
365
- write_transcript "$T_PROSE" "user|I'll end session by 5pm|"
366
- ENV_PROSE=$(envelope_for "$OP_ID" "$T_PROSE")
362
+ T_CURRENT=$(mktemp); TMPFILES+=("$T_CURRENT")
363
+ write_transcript "$T_CURRENT" \
364
+ "user|hi|" \
365
+ "assistant|hello|" \
366
+ "user|/insight|"
367
+ ENV_CURRENT=$(envelope_for "$OP_ID" "$T_CURRENT" "/insight")
367
368
  : > "$REQ_LOG"
368
- run_hook "admin" "" "$ENV_PROSE"
369
- [[ "$HOOK_RC" -eq 0 ]] || fail "case-11 expected rc=0 (prose must not trigger), got $HOOK_RC"
370
- if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=no-intent-match$"; then
371
- pass "case-11 embedded prose 'end session' does not trigger gate"
369
+ run_hook "admin" "" "$ENV_CURRENT"
370
+ [[ "$HOOK_RC" -eq 0 ]] || fail "case-13 rc=$HOOK_RC"
371
+ if ingest_lines | grep -qE "^prior-incomplete"; then
372
+ fail "case-13 current /insight turn must not self-report prior-incomplete, got: $(ingest_lines)"
373
+ elif ingest_lines | grep -qE "^trigger sessionId=${OP_ID} token=/insight$"; then
374
+ pass "case-13 current /insight (in transcript, no response yet) → trigger only, no prior-incomplete"
372
375
  else
373
- fail "case-11 expected no-intent-match, got: $(ingest_lines)"
376
+ fail "case-13 expected trigger without prior-incomplete, got: $(ingest_lines)"
374
377
  fi
375
378
 
376
379
  # ---------------------------------------------------------------------------
377
- # Case 12: case-insensitive match`/END` triggers like `/end`
380
+ # Case 12: no path returns exit 2 /insight must not block the turn
378
381
  # ---------------------------------------------------------------------------
379
- T_CASE=$(mktemp); TMPFILES+=("$T_CASE")
380
- write_transcript "$T_CASE" "user|/END|"
381
- ENV_CASE=$(envelope_for "$OP_ID" "$T_CASE")
382
382
  : > "$REQ_LOG"
383
- run_hook "admin" "" "$ENV_CASE"
384
- [[ "$HOOK_RC" -eq 2 ]] || fail "case-12 expected rc=2 for /END, got $HOOK_RC"
385
- if ingest_lines | grep -qE "^trigger sessionId=${OP_ID} token=/end$"; then
386
- pass "case-12 case-insensitive: /END normalises to /end"
383
+ run_hook "admin" "" "$ENV_INSIGHT"
384
+ if [[ "$HOOK_RC" -eq 2 ]]; then
385
+ fail "case-12 hook must never exit 2 (run-and-continue), got rc=$HOOK_RC"
387
386
  else
388
- fail "case-12 expected token=/end, got: $(ingest_lines)"
387
+ pass "case-12 trigger path exits 0, never 2"
389
388
  fi
390
389
 
391
390
  # ---------------------------------------------------------------------------
392
391
  # Summary
393
392
  # ---------------------------------------------------------------------------
394
393
  echo
395
- echo "session-end-retrospective tests: $PASS passed, $FAIL failed"
394
+ echo "insight tests: $PASS passed, $FAIL failed"
396
395
  [[ "$FAIL" -eq 0 ]]