@pmelab/gtd 7.5.0 → 7.6.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.
package/README.md CHANGED
@@ -181,7 +181,11 @@ code), waits for you to save and exit, then captures your edit itself and keeps
181
181
  driving — or halts if you left nothing changed — so you never run
182
182
  `gtd step human` by hand. Pass `--no-edit` (or set `GTD_NO_EDIT`) to fall back
183
183
  to halting and printing the gate instead, if you'd rather edit and re-launch it
184
- yourself. See [Driving the loop](docs/loop.md).
184
+ yourself. Bare `gtd` prints one line per event — colored and emoji on a real
185
+ terminal, plain ASCII under `NO_COLOR` or when piped — and redirects the noisier
186
+ agent/check/step subprocess output to a per-repo/per-worktree log file.
187
+ `gtd log` opens that logfile in your editor. See
188
+ [Driving the loop](docs/loop.md).
185
189
 
186
190
  Before wiring gtd into a repo, note the
187
191
  [repository requirements](docs/cli.md#repository-requirements) — most
package/bin/gtd CHANGED
@@ -10,6 +10,10 @@ set -euo pipefail
10
10
  # - `edit [path]` -> handled entirely here in bash, never forwarded to the
11
11
  # bundle: opens `${VISUAL:-$EDITOR}` on `path`, or (no `path` given) on the
12
12
  # resolved rest's `.file` (or `.` when absent).
13
+ # - `log` -> handled entirely here in bash, never forwarded to the bundle:
14
+ # opens `${VISUAL:-$EDITOR}` on the current repo/worktree's loop logfile
15
+ # (see `resolve_log_path`), refusing rather than opening an empty buffer
16
+ # when no loop has produced one yet.
13
17
  # - no arguments, or exactly `loop` with no further arguments -> fall through
14
18
  # into the loop body below (the loop protocol comment further down).
15
19
  # - `loop` with additional arguments (other than a lone `--no-edit`) ->
@@ -51,6 +55,147 @@ launch_editor() {
51
55
  bash -c "$editor \"\$1\"" gtd-edit "$target"
52
56
  }
53
57
 
58
+ # ── Rendering primitives (capability detection, palette/markers, emitters) ──
59
+ # Additive: nothing below calls these yet. `FANCY=1` only when stdout (fd 1)
60
+ # is a real terminal AND `NO_COLOR` is unset — any pipe/redirect/non-tty
61
+ # stdout, or `NO_COLOR` set to any value, gives plain ASCII with no escape
62
+ # codes, per the NO_COLOR convention (https://no-color.org).
63
+ if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then FANCY=1; else FANCY=0; fi
64
+
65
+ if [[ "$FANCY" == 1 ]]; then
66
+ C_RESET=$'\e[0m'; C_BOLD=$'\e[1m'; C_DIM=$'\e[2m'
67
+ C_CYAN=$'\e[36m'; C_GREEN=$'\e[32m'; C_YELLOW=$'\e[33m'; C_RED=$'\e[31m'
68
+ M_TRANS="➡️"; M_AGENT="🤖"; M_CHECK="🔧"; M_HUMAN="✍️"
69
+ M_FIX="🔁"; M_COMMIT="✅"; M_SETTLE="🎉"; M_ERROR="❌"
70
+ else
71
+ C_RESET=""; C_BOLD=""; C_DIM=""
72
+ C_CYAN=""; C_GREEN=""; C_YELLOW=""; C_RED=""
73
+ M_TRANS="->"; M_AGENT="[agent]"; M_CHECK="[check]"; M_HUMAN="[you]"
74
+ M_FIX="[fix]"; M_COMMIT="[commit]"; M_SETTLE="[done]"; M_ERROR="[err]"
75
+ fi
76
+
77
+ # emit_transition <from> <to> <files> — a captured state-to-state move. The
78
+ # "→" separator is literal (not FANCY-dependent) — the same separator the
79
+ # engine's commit grammar itself uses, per the package spec.
80
+ emit_transition() {
81
+ local from="$1" to="$2" files="$3"
82
+ if [[ -n "$files" ]]; then
83
+ printf '%s %s%s → %s%s (%s%s%s)\n' \
84
+ "$M_TRANS" "$C_BOLD$C_CYAN" "$from" "$to" "$C_RESET" \
85
+ "$C_DIM" "$files" "$C_RESET"
86
+ else
87
+ printf '%s %s%s → %s%s\n' \
88
+ "$M_TRANS" "$C_BOLD$C_CYAN" "$from" "$to" "$C_RESET"
89
+ fi
90
+ }
91
+
92
+ # emit_action <marker> <label> — the heartbeat printed before a turn runs.
93
+ emit_action() {
94
+ local marker="$1" label="$2"
95
+ printf '%s %s%s\n' "$marker" "$label" "$C_RESET"
96
+ }
97
+
98
+ # emit_commit <subject> <files> — a non-transition capture (a bare-subject
99
+ # self-loop, or the squash's agent-authored final commit).
100
+ emit_commit() {
101
+ local subject="$1" files="$2"
102
+ if [[ -n "$files" ]]; then
103
+ printf '%s %s%s%s (%s%s%s)\n' "$M_COMMIT" "$C_GREEN" "$subject" "$C_RESET" \
104
+ "$C_DIM" "$files" "$C_RESET"
105
+ else
106
+ printf '%s %s%s%s\n' "$M_COMMIT" "$C_GREEN" "$subject" "$C_RESET"
107
+ fi
108
+ }
109
+
110
+ # emit_gate <state> <message> — the human-instruction line at a message rest.
111
+ emit_gate() {
112
+ local message="$2"
113
+ printf '%s %s%s%s\n' "$M_HUMAN" "$C_BOLD$C_YELLOW" "$message" "$C_RESET"
114
+ }
115
+
116
+ # emit_settled <state> — the terminal "nothing left to do" signal.
117
+ emit_settled() {
118
+ printf '%s %ssettled — nothing left to do%s\n' "$M_SETTLE" "$C_GREEN" "$C_RESET"
119
+ }
120
+
121
+ # emit_error <message> — refusals/stalls/failures, to stderr; <message> may be
122
+ # multi-line and is passed through verbatim.
123
+ emit_error() {
124
+ local message="$1"
125
+ printf '%s %s%s%s\n' "$M_ERROR" "$C_RED" "$message" "$C_RESET" >&2
126
+ }
127
+
128
+ # emit_log_pointer — the "see the log" line printed right after every
129
+ # emit_error call, to stderr: suppression must never hide a failure, so every
130
+ # error points at where the redirected subprocess output actually went.
131
+ emit_log_pointer() {
132
+ if [[ "$FANCY" == 1 ]]; then
133
+ printf '%s📄 see %s%s\n' "$C_DIM" "$GTD_LOOP_LOG" "$C_RESET" >&2
134
+ else
135
+ printf '%ssee %s%s\n' "$C_DIM" "$GTD_LOOP_LOG" "$C_RESET" >&2
136
+ fi
137
+ }
138
+
139
+ # ── Git-derivation helpers (parse_subject, report_commits) ──────────────────
140
+ # Additive: nothing below calls these yet. Replaces today's
141
+ # head_before/head_after string comparisons in a later change.
142
+
143
+ # parse_subject <subject> — sets S_ACTOR / S_FROM / S_TO / S_KIND. Patterns are
144
+ # held in variables (rather than inlined) so bash's [[ =~ ]] doesn't need the
145
+ # literal parens/spaces quoted inline.
146
+ parse_subject() {
147
+ local subject="$1"
148
+ local transition_re='^gtd\(([^)]+)\): (.+) → (.+)$'
149
+ local capture_re='^gtd\(([^)]+)\): (.+)$'
150
+ S_ACTOR="" S_FROM="" S_TO="" S_KIND=""
151
+ if [[ "$subject" =~ $transition_re ]]; then
152
+ S_KIND=transition
153
+ S_ACTOR="${BASH_REMATCH[1]}"
154
+ S_FROM="${BASH_REMATCH[2]}"
155
+ S_TO="${BASH_REMATCH[3]}"
156
+ elif [[ "$subject" =~ $capture_re ]]; then
157
+ S_KIND=capture
158
+ S_ACTOR="${BASH_REMATCH[1]}"
159
+ S_TO="${BASH_REMATCH[2]}"
160
+ else
161
+ S_KIND=squash
162
+ S_TO="$subject"
163
+ fi
164
+ }
165
+
166
+ # report_commits <head_before> — walks <head_before>..HEAD in order, emitting
167
+ # a transition/commit line per new commit; returns non-zero (having emitted
168
+ # nothing) when there were no new commits.
169
+ report_commits() {
170
+ local head_before="$1" sha subject files reported=0
171
+ while IFS= read -r sha; do
172
+ [[ -z "$sha" ]] && continue
173
+ reported=1
174
+ subject="$(git log -1 --format=%s "$sha")"
175
+ files="$(git diff-tree --no-commit-id --name-only -r "$sha" | paste -sd, - | sed 's/,/, /g')"
176
+ parse_subject "$subject"
177
+ case "$S_KIND" in
178
+ transition) emit_transition "$S_FROM" "$S_TO" "$files" ;;
179
+ capture | squash) emit_commit "$subject" "$files" ;;
180
+ esac
181
+ done < <(git rev-list --reverse "$head_before"..HEAD)
182
+ [[ "$reported" == 1 ]]
183
+ }
184
+
185
+ # ── Log file (resolve_log_path, gtd log) ────────────────────────────────────
186
+
187
+ # resolve_log_path — echoes the loop's logfile path: $GTD_LOOP_LOG verbatim if
188
+ # set, else "$(git rev-parse --git-dir)/gtd-loop.log" (per-repository AND
189
+ # per-worktree automatically, since a linked worktree's git-dir is its own
190
+ # private .git/worktrees/<name>).
191
+ resolve_log_path() {
192
+ if [[ -n "${GTD_LOOP_LOG:-}" ]]; then
193
+ echo "$GTD_LOOP_LOG"
194
+ else
195
+ echo "$(git rev-parse --git-dir)/gtd-loop.log"
196
+ fi
197
+ }
198
+
54
199
  # --no-edit / GTD_NO_EDIT govern only the loop's automatic editor launching at
55
200
  # human gates (see handle_human_gate further down) — `gtd edit` itself never
56
201
  # reads either. `--no-edit` is recognised in exactly the two sanctioned
@@ -82,6 +227,21 @@ if [[ "${1:-}" == "edit" ]]; then
82
227
  exit 0
83
228
  fi
84
229
 
230
+ if [[ "${1:-}" == "log" ]]; then
231
+ shift
232
+ if [[ $# -gt 0 ]]; then
233
+ echo "gtd log: takes no arguments" >&2
234
+ exit 2
235
+ fi
236
+ log_path="$(resolve_log_path)"
237
+ if [[ ! -f "$log_path" ]]; then
238
+ echo "gtd log: no loop log yet at $log_path" >&2
239
+ exit 1
240
+ fi
241
+ launch_editor "$log_path"
242
+ exit 0
243
+ fi
244
+
85
245
  if [[ $# -gt 0 && "$1" != "loop" ]]; then
86
246
  run_gtd "$@"
87
247
  exit $?
@@ -166,8 +326,8 @@ herdr_release() { # hand the pane back to Herdr's normal detection
166
326
  # opening move (when editing is on) and by every "message" rest the main loop
167
327
  # encounters. $1 is the rest's `next --json` blob.
168
328
  #
169
- # Editing OFF ($edit_disabled): today's behavior, unchanged — print the gate
170
- # and exit 0 without touching an editor.
329
+ # Editing OFF ($edit_disabled): today's behavior, unchanged — render the gate
330
+ # via emit_gate and exit 0 without touching an editor.
171
331
  #
172
332
  # Editing ON (the default): open the editor on the rest's `.file` (or `.` when
173
333
  # absent), wait for it to exit, then run `gtd step human`:
@@ -187,7 +347,7 @@ handle_human_gate() {
187
347
  if [[ "$edit_disabled" == 1 ]]; then
188
348
  herdr_report blocked "$label"
189
349
  herdr_notify "gtd needs you — $(basename "$PWD")" "$label"
190
- echo "--- Your turn ($state) ---"
350
+ emit_gate "$state" "$label"
191
351
  run_gtd next
192
352
  exit 0
193
353
  fi
@@ -198,19 +358,22 @@ handle_human_gate() {
198
358
 
199
359
  head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
200
360
  if ! step_out="$(run_gtd step human 2>&1)"; then
201
- echo "gtd-loop: could not capture your changes at the human gate:" >&2
202
- echo "$step_out" >&2
203
- echo "Fix the reported issue and re-run gtd-loop." >&2
361
+ emit_error "could not capture your changes at the human gate:
362
+ $step_out
363
+ Fix the reported issue and re-run gtd-loop."
364
+ emit_log_pointer
204
365
  exit 1
205
366
  fi
206
367
  head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
207
368
  if [[ "$head_before" == "$head_after" ]]; then
208
369
  herdr_report idle "$label"
209
370
  herdr_release
210
- echo "--- Done for now ($state: nothing captured) ---"
371
+ emit_action "$M_HUMAN" "done for now (nothing captured)"
211
372
  exit 0
212
373
  fi
213
- # else: a new commit was captured — return normally, caller keeps driving.
374
+ # A new commit was captured — render it, then return normally so the
375
+ # caller keeps driving.
376
+ report_commits "$head_before"
214
377
  }
215
378
 
216
379
  trap 'code=$?; if [ "$code" -ne 0 ]; then
@@ -224,6 +387,17 @@ prev_content=""
224
387
  # across iterations and across restarts. In the git dir, never the work tree.
225
388
  memory_marker="$(git rev-parse --git-dir 2>/dev/null || echo .git)/gtd-loop-memory"
226
389
 
390
+ # Truncated once here (not every iteration), so the file always reflects the
391
+ # current run but survives after the loop exits for a later `gtd log`.
392
+ log_path="$(resolve_log_path)"
393
+ : >"$log_path"
394
+ export GTD_LOOP_LOG="$log_path"
395
+ if [[ "$FANCY" == 1 ]]; then
396
+ printf '%s📄 log: %s (tail -f to follow)%s\n' "$C_DIM" "$log_path" "$C_RESET"
397
+ else
398
+ printf '%slog: %s (tail -f to follow)%s\n' "$C_DIM" "$log_path" "$C_RESET"
399
+ fi
400
+
227
401
  # Swappable agent adapter: GTD_LOOP_AGENT_CMD lets any coding agent CLI stand
228
402
  # in for the default, receiving the prompt via $GTD_LOOP_PROMPT, the state's
229
403
  # opaque model hint (if any) via $GTD_LOOP_MODEL, and the memory scope via
@@ -249,7 +423,8 @@ run_agent_turn() {
249
423
  cmd="$cmd"' --dangerously-skip-permissions'
250
424
  fi
251
425
  GTD_LOOP_PROMPT="$prompt" GTD_LOOP_MODEL="$model" GTD_LOOP_MEMORY="$memory" \
252
- GTD_LOOP_SESSION_ID="$session_id" GTD_LOOP_MEMORY_RESUME="$do_resume" bash -c "$cmd"
426
+ GTD_LOOP_SESSION_ID="$session_id" GTD_LOOP_MEMORY_RESUME="$do_resume" bash -c "$cmd" \
427
+ >>"${GTD_LOOP_LOG:-/dev/null}" 2>&1
253
428
  }
254
429
 
255
430
  # Opening move: capture the human's pending edit so gtd-loop is the ONLY command
@@ -273,17 +448,19 @@ run_agent_turn() {
273
448
  # step silently, refusal surfaced and halts, a clean/committed gate just falls
274
449
  # into the loop below.
275
450
  if ! peek_json="$(run_gtd next --json 2>&1)"; then
276
- echo "gtd-loop: could not determine the next step:" >&2
277
- echo "$peek_json" >&2
278
- echo "Run \`gtd status\` to inspect the repo." >&2
451
+ emit_error "could not determine the next step:
452
+ $peek_json
453
+ Run \`gtd status\` to inspect the repo."
454
+ emit_log_pointer
279
455
  exit 1
280
456
  fi
281
457
  if [[ "$(jq -r .kind <<<"$peek_json")" == "message" ]]; then
282
458
  if [[ "$edit_disabled" == 1 ]]; then
283
459
  if ! step_out="$(run_gtd step human 2>&1)"; then
284
- echo "gtd-loop: could not capture your changes at the human gate:" >&2
285
- echo "$step_out" >&2
286
- echo "Fix the reported issue and re-run gtd-loop." >&2
460
+ emit_error "could not capture your changes at the human gate:
461
+ $step_out
462
+ Fix the reported issue and re-run gtd-loop."
463
+ emit_log_pointer
287
464
  exit 1
288
465
  fi
289
466
  else
@@ -318,18 +495,24 @@ while true; do
318
495
  herdr_report working "$label"
319
496
 
320
497
  if [[ "$kind" == "script" ]]; then
498
+ emit_action "$M_CHECK" "$label"
321
499
  head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
322
500
  # Execute the emitted wrapper verbatim; its exit code is deliberately
323
501
  # ignored (`|| true` under `set -e`) — the script encodes its outcome in
324
502
  # the tree, and `gtd step` captures whatever it left there via the state's
325
- # own `on` patterns.
326
- bash -c "$content" || true
327
- run_gtd step "$actor" >/dev/null
328
- head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
329
- if [[ "$head_before" == "$head_after" ]]; then
503
+ # own `on` patterns. Its own output goes to the log, not the terminal.
504
+ bash -c "$content" >>"$GTD_LOOP_LOG" 2>&1 || true
505
+ if ! step_out="$(run_gtd step "$actor" 2>&1)"; then
506
+ printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
507
+ emit_error "$step_out"
508
+ emit_log_pointer
509
+ exit 1
510
+ fi
511
+ printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
512
+ if ! report_commits "$head_before"; then
330
513
  herdr_report idle "$label"
331
514
  herdr_release
332
- echo "--- Settled ($state: check passed, nothing to do) ---"
515
+ emit_settled "$state"
333
516
  exit 0
334
517
  fi
335
518
  prev_state=""
@@ -341,8 +524,10 @@ while true; do
341
524
  # agent-owned state and content repeating means the last dispatch made no
342
525
  # progress; stop rather than spin on it.
343
526
  if [[ "$state" == "$prev_state" && "$content" == "$prev_content" ]]; then
344
- echo "gtd-loop: no progress at '$state' — the agent's last turn changed nothing. Stopping to avoid spinning." >&2
345
- run_gtd next >&2
527
+ next_out="$(run_gtd next 2>&1)" || true
528
+ emit_error "no progress at '$state' — the agent's last turn changed nothing. Stopping to avoid spinning.
529
+ $next_out"
530
+ emit_log_pointer
346
531
  exit 1
347
532
  fi
348
533
  prev_state="$state"
@@ -382,23 +567,37 @@ while true; do
382
567
  # cap) before stepping — the plain-`gtd next` self-validation instruction's
383
568
  # driver-side equivalent, so the next rest is only ever handed a well-formed
384
569
  # file. gtd itself never validates at step time — this is the loop's job.
570
+ emit_action "$M_AGENT" "$label"
571
+ head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
385
572
  run_agent_turn "$content" "$resume"
386
573
 
387
574
  fix_attempt=0
388
575
  while ! validate_out="$(run_gtd validate 2>&1)"; do
389
576
  fix_attempt=$((fix_attempt + 1))
390
577
  if ((fix_attempt > 3)); then
391
- echo "gtd-loop: '$state' still fails \`gtd validate\` after 3 fix attempts:" >&2
392
- echo "$validate_out" >&2
393
- echo "Stopping rather than stepping with a malformed steering file." >&2
578
+ emit_error "'$state' still fails \`gtd validate\` after 3 fix attempts:
579
+ $validate_out
580
+ Stopping rather than stepping with a malformed steering file."
581
+ emit_log_pointer
394
582
  exit 1
395
583
  fi
396
584
  # Resume the same session when there is one (the agent just wrote the file);
397
585
  # otherwise a fresh invocation still gets the findings via the prompt.
398
586
  fix_resume=0
399
587
  [[ -n "$session_id" ]] && fix_resume=1
588
+ emit_action "$M_FIX" "attempt $fix_attempt"
400
589
  run_agent_turn "$content"$'\n\n'"Your last turn does not pass \`gtd validate\`. Fix these format violations, then finish:"$'\n'"$validate_out" "$fix_resume"
401
590
  done
402
591
 
403
- run_gtd step "$actor" >/dev/null
592
+ if ! step_out="$(run_gtd step "$actor" 2>&1)"; then
593
+ printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
594
+ emit_error "$step_out"
595
+ emit_log_pointer
596
+ exit 1
597
+ fi
598
+ printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
599
+ # A no-op step (nothing matched a declared pattern on a clean tree) reports
600
+ # zero commits here — report_commits returns non-zero, and that's fine: the
601
+ # NEXT iteration's stall check is what catches unchanged state/content.
602
+ report_commits "$head_before" || true
404
603
  done
package/docs/loop.md CHANGED
@@ -137,7 +137,7 @@ while true; do
137
137
  content="$(jq -r .content <<<"$next_json")"
138
138
 
139
139
  if [[ "$kind" == "message" ]]; then
140
- echo "--- Your turn ($state) ---"
140
+ echo "your turn ($state)" # bin/gtd renders this via emit_gate instead
141
141
  gtd next
142
142
  exit 0
143
143
  fi
@@ -148,7 +148,7 @@ while true; do
148
148
  gtd step "$actor" >/dev/null # capture whatever it left in the tree
149
149
  head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
150
150
  if [[ "$head_before" == "$head_after" ]]; then
151
- echo "--- Settled ($state: check passed, nothing to do) ---"
151
+ echo "settled ($state: check passed, nothing to do)" # bin/gtd renders this via emit_settled instead
152
152
  exit 0
153
153
  fi
154
154
  continue
@@ -164,6 +164,29 @@ The driver — not the prompt text — owns ending the agent's turn
164
164
  (`gtd step "$actor"` right after the agent acts): every default-workflow agent
165
165
  prompt says explicitly not to run `gtd step agent` itself.
166
166
 
167
+ `bin/gtd log` opens the current repo/worktree's loop logfile —
168
+ `"$(git rev-parse --git-dir)/gtd-loop.log"` by default, or `$GTD_LOOP_LOG`
169
+ verbatim when set — in `${VISUAL:-$EDITOR}`, the same editor resolution
170
+ `bin/gtd edit` uses. It takes no arguments and errors out (rather than opening
171
+ an empty buffer) if no loop has produced a log yet.
172
+
173
+ ## Rendered output and logging
174
+
175
+ `bin/gtd` detects its output capability once at startup (`FANCY`, true when
176
+ stdout is a real terminal AND `NO_COLOR` is unset): on a real terminal it prints
177
+ one colored/emoji line per event, and under `NO_COLOR` or a piped/redirected
178
+ stdout it prints the same events as plain ASCII with no escape codes, per the
179
+ [NO_COLOR convention](https://no-color.org). Normal events (a state transition,
180
+ a captured commit, an agent/check turn starting, settling at idle) render to
181
+ stdout; refusals, stalls, and validate-cap halts render to stderr, each followed
182
+ by a `📄 see <log>` pointer (plain: `see <log>`).
183
+
184
+ The three previously terminal-visible subprocess streams — the agent turn's own
185
+ output, the check script's own output, and `gtd step`'s own output — are no
186
+ longer printed directly: they're appended to the per-repo/per-worktree log file
187
+ (truncated once at the start of the run) instead, and surfaced with
188
+ `bin/gtd log` above.
189
+
167
190
  ## Agent memory scope
168
191
 
169
192
  A `"prompt"` beat may carry an optional `memory` key — an opaque scope label the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "7.5.0",
3
+ "version": "7.6.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {