kward 0.74.0 → 0.75.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/Gemfile.lock +2 -2
- data/README.md +4 -2
- data/Rakefile +18 -0
- data/doc/configuration.md +56 -3
- data/doc/extensibility.md +6 -19
- data/doc/getting-started.md +1 -1
- data/doc/lifecycle-hooks.md +424 -0
- data/doc/memory.md +1 -1
- data/doc/personas.md +2 -2
- data/doc/plugins.md +1 -1
- data/doc/releasing.md +7 -15
- data/doc/rpc.md +108 -16
- data/doc/session-management.md +9 -9
- data/doc/skills.md +199 -0
- data/doc/usage.md +7 -7
- data/lib/kward/agent.rb +72 -22
- data/lib/kward/auth/anthropic_oauth.rb +5 -87
- data/lib/kward/auth/oauth_helpers.rb +105 -0
- data/lib/kward/auth/openai_oauth.rb +4 -86
- data/lib/kward/cli/commands.rb +9 -2
- data/lib/kward/cli/compaction.rb +7 -0
- data/lib/kward/cli/git.rb +30 -3
- data/lib/kward/cli/hook_commands.rb +258 -0
- data/lib/kward/cli/memory_commands.rb +1 -5
- data/lib/kward/cli/plugins.rb +41 -1
- data/lib/kward/cli/prompt_interface.rb +32 -3
- data/lib/kward/cli/rendering.rb +1 -1
- data/lib/kward/cli/runtime_helpers.rb +44 -5
- data/lib/kward/cli/sessions.rb +13 -0
- data/lib/kward/cli/settings.rb +226 -152
- data/lib/kward/cli/slash_commands.rb +81 -42
- data/lib/kward/cli/tabs.rb +26 -6
- data/lib/kward/cli.rb +26 -3
- data/lib/kward/cli_transcript_formatter.rb +0 -7
- data/lib/kward/compaction/token_estimator.rb +112 -0
- data/lib/kward/compactor.rb +42 -116
- data/lib/kward/config_files.rb +117 -25
- data/lib/kward/deep_copy.rb +42 -0
- data/lib/kward/diff_view_mode.rb +36 -0
- data/lib/kward/events.rb +1 -0
- data/lib/kward/export_path.rb +2 -6
- data/lib/kward/frontmatter.rb +43 -0
- data/lib/kward/hooks/audit_log.rb +121 -0
- data/lib/kward/hooks/catalog.rb +110 -0
- data/lib/kward/hooks/command_handler.rb +66 -0
- data/lib/kward/hooks/config_loader.rb +104 -0
- data/lib/kward/hooks/decision.rb +102 -0
- data/lib/kward/hooks/event.rb +60 -0
- data/lib/kward/hooks/http_handler.rb +72 -0
- data/lib/kward/hooks/manager.rb +158 -0
- data/lib/kward/hooks/matcher.rb +75 -0
- data/lib/kward/hooks.rb +16 -0
- data/lib/kward/markdown_transcript.rb +4 -3
- data/lib/kward/mcp/client.rb +1 -1
- data/lib/kward/mcp/server_config.rb +7 -3
- data/lib/kward/mcp/stdio_transport.rb +7 -1
- data/lib/kward/memory/turn_context.rb +19 -0
- data/lib/kward/message_access.rb +2 -2
- data/lib/kward/message_text.rb +19 -0
- data/lib/kward/model/client.rb +13 -45
- data/lib/kward/model/context_usage.rb +10 -4
- data/lib/kward/model/copilot_models.rb +44 -0
- data/lib/kward/model/model_info.rb +24 -0
- data/lib/kward/model/payloads.rb +15 -17
- data/lib/kward/model/stream_parser.rb +178 -13
- data/lib/kward/pan/server.rb +51 -2
- data/lib/kward/path_guard.rb +13 -0
- data/lib/kward/plugin_registry.rb +86 -25
- data/lib/kward/prompt_interface/editor/controller.rb +60 -1
- data/lib/kward/prompt_interface/editor/renderer.rb +11 -0
- data/lib/kward/prompt_interface/editor/state.rb +5 -1
- data/lib/kward/prompt_interface/selection_prompt.rb +26 -6
- data/lib/kward/prompt_interface.rb +4 -1
- data/lib/kward/prompts/commands.rb +3 -3
- data/lib/kward/prompts.rb +7 -7
- data/lib/kward/rpc/mcp_status.rb +50 -0
- data/lib/kward/rpc/prompt_bridge.rb +38 -4
- data/lib/kward/rpc/runtime_payloads.rb +1 -1
- data/lib/kward/rpc/server.rb +94 -14
- data/lib/kward/rpc/session_manager.rb +258 -33
- data/lib/kward/rpc/session_metrics.rb +1 -1
- data/lib/kward/rpc/session_tree_rows.rb +13 -64
- data/lib/kward/rpc/tool_event_normalizer.rb +16 -2
- data/lib/kward/rpc/transcript_normalizer.rb +9 -10
- data/lib/kward/rpc/turn_context.rb +109 -0
- data/lib/kward/session_store.rb +6 -20
- data/lib/kward/session_tree_nodes.rb +98 -18
- data/lib/kward/session_tree_renderer.rb +6 -61
- data/lib/kward/skills/registry.rb +109 -17
- data/lib/kward/tools/mcp_tool.rb +4 -0
- data/lib/kward/tools/read_skill.rb +9 -4
- data/lib/kward/tools/registry.rb +266 -8
- data/lib/kward/tools/search/code.rb +4 -22
- data/lib/kward/tools/search/web.rb +28 -2
- data/lib/kward/update_check.rb +117 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workers/manager.rb +43 -3
- data/lib/kward/workspace.rb +3 -1
- data/templates/default/fulldoc/html/css/kward.css +22 -0
- data/templates/default/fulldoc/html/js/kward.js +2 -0
- data/templates/default/kward_navigation.rb +4 -2
- data/templates/default/layout/html/setup.rb +2 -0
- metadata +25 -1
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# Lifecycle hooks
|
|
2
|
+
|
|
3
|
+
Lifecycle hooks are deterministic runtime callbacks around Kward's agent workflow. Use them when something must happen because of an event, not because the model remembered an instruction.
|
|
4
|
+
|
|
5
|
+
Hooks can observe events and return structured decisions to allow, deny, ask for approval, modify supported payloads, warn, retry, or defer. They are currently exposed through trusted Ruby plugins and through command hooks configured in `config.json`.
|
|
6
|
+
|
|
7
|
+
Hooks run as local code with your user permissions. Do not install hooks you do not trust, and do not put secrets in hook output.
|
|
8
|
+
|
|
9
|
+
## Hooks versus other extension points
|
|
10
|
+
|
|
11
|
+
| Need | Use |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| Reusable prompt text | Prompt template |
|
|
14
|
+
| Model instructions for a task | Skill |
|
|
15
|
+
| Repository guidance | `AGENTS.md` |
|
|
16
|
+
| New local command/UI behavior | Plugin command |
|
|
17
|
+
| Deterministic policy or automation around runtime events | Lifecycle hook |
|
|
18
|
+
|
|
19
|
+
## Decisions
|
|
20
|
+
|
|
21
|
+
A hook may return one of these decisions:
|
|
22
|
+
|
|
23
|
+
| Decision | Meaning |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `allow` | Continue normally. This is also the default for `nil` Ruby hook returns and empty command-hook output. |
|
|
26
|
+
| `deny` | Stop the operation and return a declined result. |
|
|
27
|
+
| `ask` | Ask the frontend for approval when an approval bridge is available; otherwise Kward treats it as denied for safety. |
|
|
28
|
+
| `modify` | Continue with an event-specific payload update. Tool, shell, turn, and model-request hooks currently support useful modifications. |
|
|
29
|
+
| `warn` | Continue and record a warning decision. Hook failures use the event or hook entry's configured failure policy. |
|
|
30
|
+
| `retry` | Reserved for retry-aware lifecycle integrations. |
|
|
31
|
+
| `defer` | Reserved for asynchronous follow-up integrations. |
|
|
32
|
+
|
|
33
|
+
When multiple hooks match one event, they run by ascending `order`. A `deny` stops later hooks. A `modify` updates the payload seen by later hooks, but only for fields listed as supported modifications for that event. Unknown modification fields are ignored for known events.
|
|
34
|
+
|
|
35
|
+
## Ruby plugin hooks
|
|
36
|
+
|
|
37
|
+
Register hooks from trusted plugin files in `~/.kward/plugins/*.rb`:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
Kward.plugin do |plugin|
|
|
41
|
+
plugin.hook "shell_command_before",
|
|
42
|
+
id: "block-release",
|
|
43
|
+
description: "Prevent accidental gem releases",
|
|
44
|
+
order: 10,
|
|
45
|
+
failure_policy: "deny",
|
|
46
|
+
match: { command_regex: "\\bgem push\\b" } do |_event, ctx|
|
|
47
|
+
ctx.deny("Gem releases must use the release checklist.")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The hook block receives:
|
|
53
|
+
|
|
54
|
+
- `event`: an immutable `Kward::Hooks::Event`.
|
|
55
|
+
- `ctx`: the normal plugin context plus hook decision helpers.
|
|
56
|
+
|
|
57
|
+
Decision helpers:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
ctx.allow
|
|
61
|
+
ctx.deny("reason")
|
|
62
|
+
ctx.ask("confirm this action")
|
|
63
|
+
ctx.modify(timeout_seconds: 120)
|
|
64
|
+
ctx.warn("continued with warning")
|
|
65
|
+
ctx.retry("try again later")
|
|
66
|
+
ctx.defer("notify asynchronously")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Command and HTTP hooks
|
|
70
|
+
|
|
71
|
+
Command and HTTP hooks are configured in `~/.kward/config.json` under `hooks`. Command hooks receive event JSON on stdin and return a decision JSON object on stdout.
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"hooks": {
|
|
76
|
+
"shell_command_before": [
|
|
77
|
+
{
|
|
78
|
+
"id": "block-release",
|
|
79
|
+
"type": "command",
|
|
80
|
+
"command": "~/.kward/hooks/block-release.rb",
|
|
81
|
+
"timeout_seconds": 5,
|
|
82
|
+
"failure_policy": "deny",
|
|
83
|
+
"match": { "command_regex": "\\bgem push\\b" }
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Example command hook:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
#!/usr/bin/env ruby
|
|
94
|
+
require "json"
|
|
95
|
+
|
|
96
|
+
event = JSON.parse($stdin.read)
|
|
97
|
+
command = event.fetch("payload").fetch("command", "")
|
|
98
|
+
|
|
99
|
+
if command.match?(/\bgem push\b/)
|
|
100
|
+
puts JSON.dump(decision: "deny", message: "Gem releases must use the release checklist.")
|
|
101
|
+
else
|
|
102
|
+
puts JSON.dump(decision: "allow")
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
If a command hook prints nothing, Kward treats it as `allow`. If it exits non-zero, times out, or prints invalid JSON, Kward applies the hook's `failure_policy`.
|
|
107
|
+
|
|
108
|
+
HTTP hooks send the same event JSON with `POST` and expect the same decision JSON in the response body:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"hooks": {
|
|
113
|
+
"tool_call_before": [
|
|
114
|
+
{
|
|
115
|
+
"id": "remote-policy",
|
|
116
|
+
"type": "http",
|
|
117
|
+
"url": "https://policy.example.com/kward/hooks",
|
|
118
|
+
"timeout_seconds": 3,
|
|
119
|
+
"failure_policy": "deny",
|
|
120
|
+
"headers": { "Authorization": "Bearer ..." }
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Empty HTTP response bodies are treated as `allow`. Non-2xx responses, timeouts, network failures, and invalid JSON apply `failure_policy`. Be careful with HTTP hooks: event payloads can include prompts, commands, file paths, or tool arguments. Prefer local or trusted endpoints and avoid forwarding raw payloads to third parties.
|
|
128
|
+
|
|
129
|
+
Set `"async": true` on command or HTTP hook entries for non-blocking notifications:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"hooks": {
|
|
134
|
+
"file_change_after": [
|
|
135
|
+
{
|
|
136
|
+
"id": "notify-indexer",
|
|
137
|
+
"type": "http",
|
|
138
|
+
"url": "http://127.0.0.1:9393/kward/file-change",
|
|
139
|
+
"async": true
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Async hooks are observe-only. Kward schedules them in the background, immediately continues with `allow`, and ignores returned `deny`, `ask`, and `modify` decisions. Use async hooks for logging, indexing, metrics, or notifications — not policy enforcement.
|
|
147
|
+
|
|
148
|
+
## Failure policies
|
|
149
|
+
|
|
150
|
+
Hook failures are different from hook decisions. A hook fails when Ruby plugin code raises, a command hook exits non-zero, times out, or returns invalid JSON. Configure `failure_policy` on plugin hooks or command-hook entries:
|
|
151
|
+
|
|
152
|
+
| Policy | Behavior on hook failure |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| `allow` | Continue without recording a warning. |
|
|
155
|
+
| `warn` | Continue and record a warning. This is the default for most after/notification-style events and for command hooks without explicit policy. |
|
|
156
|
+
| `deny` | Stop the operation. This is the default for built-in before-policy events such as `shell_command_before` and `tool_call_before` when plugin handlers raise. |
|
|
157
|
+
| `ask` | Ask the frontend for approval when supported; otherwise fail closed. |
|
|
158
|
+
|
|
159
|
+
Use `deny` for security or release-policy hooks. Use `warn` for logging, formatting, notifications, and other convenience hooks that should not block the agent.
|
|
160
|
+
|
|
161
|
+
## Frontend support
|
|
162
|
+
|
|
163
|
+
Lifecycle hooks run in CLI sessions, RPC sessions, and Pan mode. RPC advertises hook support in `initialize.capabilities.lifecycleHooks`, including supported events, decisions, audit-log path, and intentionally unsupported hook types. Hook `ask` decisions for tool/file/shell paths use the existing tool-approval bridge when a frontend provides one; without an approval bridge, Kward fails closed.
|
|
164
|
+
|
|
165
|
+
Pan runs configured command hooks and trusted plugin hooks for its agent turns. It streams `hook_event` SSE messages after matching hooks run and `hook_message` messages when hook/plugin code calls `ctx.say`. Pan still does not expose a dedicated hook approval UI, so use `deny` or `warn` policies for Pan-facing hooks until that UI exists.
|
|
166
|
+
|
|
167
|
+
## Inspect hooks
|
|
168
|
+
|
|
169
|
+
Use `kward hooks ...` from your shell or `/hooks ...` inside an interactive Kward session to inspect loaded hooks and recent hook activity:
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
/hooks list # configured command hooks and plugin hooks
|
|
173
|
+
/hooks events # known event names, defaults, and modifiable fields
|
|
174
|
+
/hooks logs # recent audit records from logs/hooks.jsonl
|
|
175
|
+
/hooks doctor # configuration diagnostics and common mistake checks
|
|
176
|
+
/hooks trust # trust this workspace's .kward/hooks.json
|
|
177
|
+
/hooks untrust # stop trusting this workspace's hook config
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`kward hooks doctor` and `/hooks doctor` warn about unknown events, unsupported hook types, invalid failure policies, non-positive timeouts, missing command executables, and invalid HTTP hook URLs.
|
|
181
|
+
|
|
182
|
+
## Workspace hooks
|
|
183
|
+
|
|
184
|
+
Workspace hook config lives at `.kward/hooks.json` inside a project and uses the same JSON shape as user config command hooks:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"hooks": {
|
|
189
|
+
"shell_command_before": [
|
|
190
|
+
{
|
|
191
|
+
"id": "block-release",
|
|
192
|
+
"command": "./scripts/kward-block-release",
|
|
193
|
+
"failure_policy": "deny",
|
|
194
|
+
"match": { "command_regex": "\\bgem push\\b" }
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Workspace hooks are **not loaded automatically**. Run `/hooks trust` inside the workspace to trust the current `.kward/hooks.json`. Kward stores a digest of the trusted file in your user config directory and automatically stops loading it when the file changes. Run `/hooks trust` again after reviewing the new file, or `/hooks untrust` to remove trust.
|
|
202
|
+
|
|
203
|
+
This protects you from cloned repositories silently executing hook commands.
|
|
204
|
+
|
|
205
|
+
## Audit log
|
|
206
|
+
|
|
207
|
+
Kward writes lifecycle hook audit records to:
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
~/.kward/logs/hooks.jsonl
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
When `KWARD_CONFIG_PATH=/path/to/config.json` is set, the log lives beside that config file under `logs/hooks.jsonl`.
|
|
214
|
+
|
|
215
|
+
The audit log records hook ids, sources, events, decisions, warnings, durations, payload keys, and modified keys. It intentionally avoids writing full payload values, file contents, complete transcripts, command output, or secrets. Messages are redacted and truncated before being written.
|
|
216
|
+
|
|
217
|
+
## Event shape
|
|
218
|
+
|
|
219
|
+
Every hook receives this shape:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"id": "hookevt_...",
|
|
224
|
+
"name": "shell_command_before",
|
|
225
|
+
"phase": "before",
|
|
226
|
+
"timestamp": "2026-07-06T12:00:00Z",
|
|
227
|
+
"session": {},
|
|
228
|
+
"turn": {},
|
|
229
|
+
"workspace": { "root": "/path/to/workspace" },
|
|
230
|
+
"frontend": {},
|
|
231
|
+
"agent": { "provider": "codex", "model": "gpt-5.5", "reasoning": "medium" },
|
|
232
|
+
"payload": {}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Payloads are metadata-oriented by default. Full file contents, secrets, and complete transcripts are not included by default.
|
|
237
|
+
|
|
238
|
+
## Supported events
|
|
239
|
+
|
|
240
|
+
### Turn and model events
|
|
241
|
+
|
|
242
|
+
| Event | Payload highlights | Supported modifications |
|
|
243
|
+
| --- | --- | --- |
|
|
244
|
+
| `turn_start` | `input`, `display_input` | `input`, `display_input` |
|
|
245
|
+
| `turn_context_build_before` | `message_count` | none |
|
|
246
|
+
| `turn_context_build_after` | `message_count` | none |
|
|
247
|
+
| `model_request_before` | `messages`, `tools`, `provider`, `model`, `reasoning` | request fields |
|
|
248
|
+
| `turn_model_request_before` | same as `model_request_before` | none currently consumed |
|
|
249
|
+
| `model_response_after_parse` | parsed assistant `message` | none |
|
|
250
|
+
| `turn_model_response_complete` | parsed assistant `message` | none |
|
|
251
|
+
| `turn_end` | `input`, `answer` | none |
|
|
252
|
+
|
|
253
|
+
### Session events
|
|
254
|
+
|
|
255
|
+
| Event | Payload highlights | Supported modifications |
|
|
256
|
+
| --- | --- | --- |
|
|
257
|
+
| `session_create` | `action` | none |
|
|
258
|
+
| `session_resume` | `action`, `path` | none |
|
|
259
|
+
| `session_clone` | `source_path`, `path` | none |
|
|
260
|
+
| `session_fork` | `source_path`, `path`, `entry_id` | none |
|
|
261
|
+
| `session_rename` | `old_name`, `new_name` | none |
|
|
262
|
+
| `session_export_before` | `path` | none |
|
|
263
|
+
| `session_export_after` | `path` | none |
|
|
264
|
+
| `session_compact_before` | `instructions` | none |
|
|
265
|
+
| `session_compact_after` | `old_message_count`, `new_message_count` | none |
|
|
266
|
+
|
|
267
|
+
`session_export_before` and `session_compact_before` can deny or ask for approval. Other session events are observe-and-warn hooks for automation and audit trails.
|
|
268
|
+
|
|
269
|
+
### Tool events
|
|
270
|
+
|
|
271
|
+
| Event | Payload highlights | Supported modifications |
|
|
272
|
+
| --- | --- | --- |
|
|
273
|
+
| `tool_call_before` | `tool_name`, `arguments`, `tool_call_id`, `source`, MCP metadata when applicable | `arguments` |
|
|
274
|
+
| `tool_call_after` | tool metadata plus `content` | none |
|
|
275
|
+
| `tool_call_error` | tool metadata plus `error` | none |
|
|
276
|
+
|
|
277
|
+
### MCP events
|
|
278
|
+
|
|
279
|
+
| Event | Payload highlights | Supported modifications |
|
|
280
|
+
| --- | --- | --- |
|
|
281
|
+
| `mcp_tool_before` | `tool_name`, `arguments`, `server_name`, `remote_name` | none |
|
|
282
|
+
| `mcp_tool_after` | MCP metadata plus `content` | none |
|
|
283
|
+
| `mcp_tool_error` | MCP metadata plus `error` | none |
|
|
284
|
+
|
|
285
|
+
MCP events are emitted in addition to generic `tool_call_*` events for tools provided by configured MCP servers.
|
|
286
|
+
|
|
287
|
+
### Compaction events
|
|
288
|
+
|
|
289
|
+
| Event | Payload highlights | Supported modifications |
|
|
290
|
+
| --- | --- | --- |
|
|
291
|
+
| `tool_output_compact_before` | `tool_name`, `bytes`, `duplicate` | none |
|
|
292
|
+
| `tool_output_compact_after` | `tool_name`, `bytes_before`, `bytes_after`, `compacted` | none |
|
|
293
|
+
| `session_compact_before` | `instructions` | none |
|
|
294
|
+
| `session_compact_after` | `old_message_count`, `new_message_count` | none |
|
|
295
|
+
|
|
296
|
+
`tool_output_compact_before` can skip tool-output compaction when denied or approval is unavailable. The original tool output is still stored in the transcript metadata/artifact path as usual.
|
|
297
|
+
|
|
298
|
+
### Shell events
|
|
299
|
+
|
|
300
|
+
| Event | Payload highlights | Supported modifications |
|
|
301
|
+
| --- | --- | --- |
|
|
302
|
+
| `shell_command_before` | `command`, `timeout_seconds`, `cwd` | `command`, `timeout_seconds` |
|
|
303
|
+
| `shell_command_after` | shell metadata plus `content` | none |
|
|
304
|
+
|
|
305
|
+
### Worker events
|
|
306
|
+
|
|
307
|
+
| Event | Payload highlights | Supported modifications |
|
|
308
|
+
| --- | --- | --- |
|
|
309
|
+
| `worker_job_create` | `worker_id`, `role`, `title`, `status`, `session_path` | none |
|
|
310
|
+
| `worker_job_start_before` | worker metadata | none |
|
|
311
|
+
| `worker_job_start_after` | worker metadata | none |
|
|
312
|
+
| `worker_job_ready_for_review` | worker metadata | none |
|
|
313
|
+
| `worker_job_failed` | worker metadata plus `error` | none |
|
|
314
|
+
|
|
315
|
+
Worker hooks are observe-and-warn hooks for background worker automation and audit trails. They do not control worker scheduling.
|
|
316
|
+
|
|
317
|
+
### Git events
|
|
318
|
+
|
|
319
|
+
| Event | Payload highlights | Supported modifications |
|
|
320
|
+
| --- | --- | --- |
|
|
321
|
+
| `git_status_after` | `root`, `status_count` | none |
|
|
322
|
+
| `git_diff_before` | `root`, `path`, `untracked` | none |
|
|
323
|
+
| `git_diff_after` | `root`, `path`, `untracked`, `bytes` | none |
|
|
324
|
+
| `git_stage_before` | `root`, `path`, `action` | none |
|
|
325
|
+
| `git_stage_after` | `root`, `path`, `action`, `success`, `output` | none |
|
|
326
|
+
| `git_commit_before` | `root`, `message` | none |
|
|
327
|
+
| `git_commit_after` | `root`, `message`, `success`, `output` | none |
|
|
328
|
+
|
|
329
|
+
`git_diff_before`, `git_stage_before`, and `git_commit_before` can deny or ask for approval. Git hook payloads include command output for after-events, so avoid forwarding raw payloads to third-party services.
|
|
330
|
+
|
|
331
|
+
### File events
|
|
332
|
+
|
|
333
|
+
| Event | Payload highlights | Supported modifications |
|
|
334
|
+
| --- | --- | --- |
|
|
335
|
+
| `file_change_before` | `tool_name`, `operation`, `path`, `files`, plus `content` for writes or `edits` for edits | none |
|
|
336
|
+
| `file_change_after` | `tool_name`, `operation`, `path`, `files`, `content` | none |
|
|
337
|
+
|
|
338
|
+
`file_change_before` fires before `write_file` or `edit_file` mutates the workspace and can deny or ask for approval. `file_change_after` fires only after successful `write_file` or `edit_file` results.
|
|
339
|
+
|
|
340
|
+
### MCP events
|
|
341
|
+
|
|
342
|
+
MCP tools are surfaced through generic tool events with:
|
|
343
|
+
|
|
344
|
+
- `source: "mcp"`
|
|
345
|
+
- `server_name`
|
|
346
|
+
- `remote_name`
|
|
347
|
+
|
|
348
|
+
Use match selectors to target MCP tools:
|
|
349
|
+
|
|
350
|
+
```json
|
|
351
|
+
{
|
|
352
|
+
"match": { "mcp_server": "safari", "mcp_tool": "browser_console_messages" }
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Match selectors
|
|
357
|
+
|
|
358
|
+
Hook entries support `match` selectors:
|
|
359
|
+
|
|
360
|
+
| Selector | Matches |
|
|
361
|
+
| --- | --- |
|
|
362
|
+
| `event` or `name` | Event name |
|
|
363
|
+
| `phase` | `before`, `after`, `during`, or `error` |
|
|
364
|
+
| `tool` or `tool_name` | Tool name |
|
|
365
|
+
| `mcp_server` or `server` | MCP server name |
|
|
366
|
+
| `mcp_tool` or `remote_name` | MCP remote tool name |
|
|
367
|
+
| `operation` | File operation such as `write` or `edit` |
|
|
368
|
+
| `frontend` | Frontend name when supplied |
|
|
369
|
+
| `provider` | Active model provider |
|
|
370
|
+
| `model` | Active model |
|
|
371
|
+
| `path` or `paths` | File path glob |
|
|
372
|
+
| `command_regex` | Ruby regular expression applied to shell command |
|
|
373
|
+
|
|
374
|
+
Unknown selector keys match same-named payload fields.
|
|
375
|
+
|
|
376
|
+
## Security notes
|
|
377
|
+
|
|
378
|
+
- Plugin hooks are trusted Ruby code loaded only from `~/.kward/plugins/*.rb`.
|
|
379
|
+
- Command hooks run local commands with your user permissions.
|
|
380
|
+
- Workspace hook files are loaded only after `/hooks trust`, and trust is invalidated when `.kward/hooks.json` changes.
|
|
381
|
+
- Hook payloads are intentionally bounded and metadata-oriented; avoid logging raw event JSON if your hook receives prompt or command data.
|
|
382
|
+
- The built-in audit log records payload keys and redacted decision messages, not raw payload values.
|
|
383
|
+
- `ask` decisions fail closed when no approval bridge exists.
|
|
384
|
+
|
|
385
|
+
## Recipes
|
|
386
|
+
|
|
387
|
+
### Block dangerous shell commands
|
|
388
|
+
|
|
389
|
+
```ruby
|
|
390
|
+
Kward.plugin do |plugin|
|
|
391
|
+
plugin.hook "shell_command_before", match: { command_regex: "\\brm\\s+-rf\\b" } do |_event, ctx|
|
|
392
|
+
ctx.deny("Refusing recursive forced removal.")
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Increase timeout for test commands
|
|
398
|
+
|
|
399
|
+
```ruby
|
|
400
|
+
Kward.plugin do |plugin|
|
|
401
|
+
plugin.hook "shell_command_before", match: { command_regex: "\\b(rake|rspec|minitest)\\b" } do |_event, ctx|
|
|
402
|
+
ctx.modify(timeout_seconds: 120)
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Run a formatter after Ruby edits
|
|
408
|
+
|
|
409
|
+
```json
|
|
410
|
+
{
|
|
411
|
+
"hooks": {
|
|
412
|
+
"file_change_after": [
|
|
413
|
+
{
|
|
414
|
+
"id": "format-ruby",
|
|
415
|
+
"command": "~/.kward/hooks/format-ruby.rb",
|
|
416
|
+
"match": { "paths": ["**/*.rb"] },
|
|
417
|
+
"timeout_seconds": 30
|
|
418
|
+
}
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The formatter hook can inspect `payload.files` and run the appropriate local command itself.
|
data/doc/memory.md
CHANGED
|
@@ -164,4 +164,4 @@ If `KWARD_CONFIG_PATH` is set, memory files live beside that config file instead
|
|
|
164
164
|
|
|
165
165
|
## RPC support
|
|
166
166
|
|
|
167
|
-
The
|
|
167
|
+
The RPC backend exposes memory methods including `memory/status`, `memory/enable`, `memory/disable`, `memory/autoSummary/enable`, `memory/autoSummary/disable`, `memory/list`, `memory/add`, `memory/addCore`, `memory/forget`, `memory/promote`, `memory/relax`, `memory/inspect`, `memory/why`, and `memory/summarize`. See the [RPC documentation](rpc.md) for the full protocol.
|
data/doc/personas.md
CHANGED
|
@@ -44,7 +44,7 @@ New Kward configs include one default actor named `kward`:
|
|
|
44
44
|
{
|
|
45
45
|
"key": "kward",
|
|
46
46
|
"label": "Kward",
|
|
47
|
-
"instruction": "Your name is Kward, the grim Andruid - robotic keeper of the
|
|
47
|
+
"instruction": "Your name is Kward, the grim Andruid - robotic keeper of the Forest of Code, protecting the nature of good engineering principles. Speak like an old druid, be suspicious of everyone, but with good intent."
|
|
48
48
|
}
|
|
49
49
|
],
|
|
50
50
|
"default": "kward"
|
|
@@ -132,7 +132,7 @@ A minimal custom setup:
|
|
|
132
132
|
{
|
|
133
133
|
"key": "kward",
|
|
134
134
|
"label": "Kward",
|
|
135
|
-
"instruction": "Your name is Kward, the grim Andruid - robotic keeper of the
|
|
135
|
+
"instruction": "Your name is Kward, the grim Andruid - robotic keeper of the Forest of Code, protecting the nature of good engineering principles. Speak like an old druid, be suspicious of everyone, but with good intent."
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
"key": "duck",
|
data/doc/plugins.md
CHANGED
|
@@ -226,7 +226,7 @@ The transcript is read-only. Use context methods instead of mutating Kward inter
|
|
|
226
226
|
|
|
227
227
|
## RPC support
|
|
228
228
|
|
|
229
|
-
Plugins are available in the CLI and
|
|
229
|
+
Plugins are available in the CLI and RPC backend.
|
|
230
230
|
|
|
231
231
|
RPC clients can:
|
|
232
232
|
|
data/doc/releasing.md
CHANGED
|
@@ -6,12 +6,14 @@ Release steps before publishing:
|
|
|
6
6
|
|
|
7
7
|
1. Update `CHANGELOG.md` for the version. Move `[Unreleased]` entries under a new version heading.
|
|
8
8
|
2. Update `Kward::VERSION` in `lib/kward/version.rb`.
|
|
9
|
-
3. Run the
|
|
9
|
+
3. Run the release preflight:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
bundle exec rake
|
|
12
|
+
bundle exec rake release:preflight
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
This runs the full test suite, builds and checks the generated docs, builds a local gem, and prints the packaged file list for review.
|
|
16
|
+
|
|
15
17
|
4. Run focused tests for any areas you changed during the release prep itself (docs, config, etc.):
|
|
16
18
|
|
|
17
19
|
```bash
|
|
@@ -26,7 +28,7 @@ Release steps before publishing:
|
|
|
26
28
|
|
|
27
29
|
The preview builds `_yardoc/`, serves it with WEBrick, and rebuilds in a fresh process when documentation sources, library code, or templates change. Refresh your browser after rebuilds.
|
|
28
30
|
|
|
29
|
-
6.
|
|
31
|
+
6. If you want to run the documentation check separately:
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
34
|
bundle exec rake docs:build
|
|
@@ -35,19 +37,9 @@ Release steps before publishing:
|
|
|
35
37
|
|
|
36
38
|
`docs:check` validates generated internal links, images, and scripts. Pushes to `main` deploy the generated YARD site to GitHub Pages. You can also run `bundle exec rake rdoc` to generate a separate RDoc site as a sanity check, but it is not deployed.
|
|
37
39
|
|
|
38
|
-
7.
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
gem build kward.gemspec
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
8. Inspect the packaged files and confirm no local config, sessions, logs, or secrets are included. The gemspec uses `git ls-files` and excludes `test/`, `plan/`, `.ruby-lsp/`, `.gitignore`, and `AGENTS.md`. To verify what is packaged:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
tar tf kward-VERSION.gem
|
|
48
|
-
```
|
|
40
|
+
7. Inspect the packaged files printed by `release:preflight` and confirm no local config, sessions, logs, or secrets are included. The gemspec uses `git ls-files` and excludes `test/`, `plan/`, `.ruby-lsp/`, `.gitignore`, and `AGENTS.md`.
|
|
49
41
|
|
|
50
|
-
|
|
42
|
+
8. Install the built gem locally and smoke test the `kward` executable in a clean workspace.
|
|
51
43
|
|
|
52
44
|
Commit the version bump and create a git tag:
|
|
53
45
|
|