@procrastivity/clast 0.0.5 → 0.0.7
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.
|
@@ -21,6 +21,27 @@ clast_porcelain_info() { printf '%s\n' "$1"; }
|
|
|
21
21
|
|
|
22
22
|
clast_porcelain_log_error() { printf 'clast: error: %s\n' "$1" >&2; }
|
|
23
23
|
|
|
24
|
+
# --- Timing ------------------------------------------------------------------
|
|
25
|
+
#
|
|
26
|
+
# Shared by the porcelain subcommands that time model calls (wake, retro) so
|
|
27
|
+
# their progress output stays consistent.
|
|
28
|
+
|
|
29
|
+
# clast_porcelain_now — epoch seconds with fraction (GNU date). Falls back to
|
|
30
|
+
# whole seconds where `%N` is unsupported (e.g. BSD/macOS date echoes a literal
|
|
31
|
+
# N). Pair two readings with clast_porcelain_elapsed.
|
|
32
|
+
clast_porcelain_now() {
|
|
33
|
+
local t
|
|
34
|
+
t="$(date +%s.%N 2>/dev/null)" || t="$(date +%s)"
|
|
35
|
+
[[ "$t" == *N* ]] && t="$(date +%s)"
|
|
36
|
+
printf '%s' "$t"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# clast_porcelain_elapsed <start> <end> — seconds between two clast_porcelain_now
|
|
40
|
+
# readings, one decimal place (never negative).
|
|
41
|
+
clast_porcelain_elapsed() {
|
|
42
|
+
awk -v a="$1" -v b="$2" 'BEGIN { d = b - a; if (d < 0) d = 0; printf "%.1f", d }'
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
# --- Version / usage ---------------------------------------------------------
|
|
25
46
|
|
|
26
47
|
# Reuses the plumbing's clast_version helper if available (loaded by sourcing
|
|
@@ -9,26 +9,43 @@
|
|
|
9
9
|
|
|
10
10
|
_clast_retrosum_usage() {
|
|
11
11
|
cat <<'EOF'
|
|
12
|
-
Usage: clast retro [--from DATE] [--to DATE] [--
|
|
13
|
-
[--refresh] [--json]
|
|
12
|
+
Usage: clast retro [--from DATE] [--to DATE] [--all]
|
|
13
|
+
[--window work-days|file-dates] [--refresh] [--json]
|
|
14
14
|
|
|
15
15
|
Condense the work retrospective (grouped by actual work day → project) into
|
|
16
16
|
model-written bullets per session. Structure is deterministic; only the prose
|
|
17
17
|
condensation calls the LLM. Summaries are cached per session under
|
|
18
18
|
<journal>/.retro-summaries/ and reused until the session content changes.
|
|
19
19
|
|
|
20
|
+
With no window flags, defaults to the last 7 days (one model call per new
|
|
21
|
+
session, so an unbounded run over the whole corpus can be slow and costly).
|
|
22
|
+
|
|
20
23
|
Flags:
|
|
21
|
-
--from DATE Start of the window (inclusive). Default:
|
|
24
|
+
--from DATE Start of the window (inclusive). Default: 7 days ago.
|
|
22
25
|
--to DATE End of the window (inclusive). Default: corpus end.
|
|
26
|
+
--all Cover the whole corpus (overrides the 7-day default).
|
|
23
27
|
--window WHICH work-days (default) | file-dates. See `clast-plumbing retro`.
|
|
24
28
|
--refresh Ignore cached summaries and re-summarize (rewrites the cache).
|
|
25
29
|
--json Emit the manifest with a `summary` per session (no render).
|
|
26
30
|
-h, --help Print this usage and exit.
|
|
27
31
|
|
|
32
|
+
DATE accepts ISO (YYYY-MM-DD), `today`, `yesterday`, `last-week`, `-Nd`, `-Nw`.
|
|
33
|
+
|
|
28
34
|
Requires the CLAST_LLM_* env vars (see `clast --help`).
|
|
29
35
|
EOF
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
# _clast_retro_progress <msg>
|
|
39
|
+
# Emit a progress line to stderr so stdout (render or --json) stays clean.
|
|
40
|
+
# Silent when CLAST_QUIET is set or stderr is not a TTY; CLAST_RETRO_PROGRESS=always
|
|
41
|
+
# forces it on (used by tests, which have no tty).
|
|
42
|
+
_clast_retro_progress() {
|
|
43
|
+
[[ -n "${CLAST_QUIET:-}" ]] && return 0
|
|
44
|
+
if [[ "${CLAST_RETRO_PROGRESS:-}" == "always" || -t 2 ]]; then
|
|
45
|
+
printf 'clast: %s\n' "$*" >&2
|
|
46
|
+
fi
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
# _clast_retrosum_fingerprint (stdin → short hex/cksum on stdout)
|
|
33
50
|
# Content fingerprint of a session body; changes invalidate the cache.
|
|
34
51
|
_clast_retrosum_fingerprint() {
|
|
@@ -58,8 +75,11 @@ _clast_retrosum_build_user() {
|
|
|
58
75
|
}
|
|
59
76
|
|
|
60
77
|
# _clast_retrosum_summary <project> <work_day> <session_id> <body> <cache_dir> <refresh>
|
|
61
|
-
# Echo the condensed summary.
|
|
62
|
-
#
|
|
78
|
+
# Echo the condensed summary. Return code tells the caller what happened so it
|
|
79
|
+
# can time only the calls that hit the model:
|
|
80
|
+
# 0 — served from cache (no model call)
|
|
81
|
+
# 3 — fresh summary via a model call (success)
|
|
82
|
+
# 1 — model call failed
|
|
63
83
|
_clast_retrosum_summary() {
|
|
64
84
|
local project="$1" work_day="$2" sid="$3" body="$4" cache_dir="$5" refresh="$6"
|
|
65
85
|
local fp cache_file cached_fp
|
|
@@ -74,6 +94,7 @@ _clast_retrosum_summary() {
|
|
|
74
94
|
fi
|
|
75
95
|
fi
|
|
76
96
|
|
|
97
|
+
_clast_retro_progress " querying model..."
|
|
77
98
|
local system user summary
|
|
78
99
|
system="$(clast_porcelain_load_system_prompt retro-summary-system)"
|
|
79
100
|
user="$(_clast_retrosum_build_user "$project" "$work_day" "$sid" "$body")"
|
|
@@ -86,6 +107,7 @@ _clast_retrosum_summary() {
|
|
|
86
107
|
>"$cache_file" 2>/dev/null || clast_porcelain_warn "failed to cache summary for $sid"
|
|
87
108
|
fi
|
|
88
109
|
printf '%s' "$summary"
|
|
110
|
+
return 3
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
# _clast_retrosum_journal_dir — resolve the journal dir (for the cache).
|
|
@@ -100,7 +122,7 @@ _clast_retrosum_journal_dir() {
|
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
clast_cmd_retro() {
|
|
103
|
-
local from="" to="" window="work-days" refresh=0 as_json=0
|
|
125
|
+
local from="" to="" window="work-days" refresh=0 as_json=0 all=0
|
|
104
126
|
|
|
105
127
|
while [[ $# -gt 0 ]]; do
|
|
106
128
|
case "$1" in
|
|
@@ -108,6 +130,7 @@ clast_cmd_retro() {
|
|
|
108
130
|
--from=*) from="${1#*=}"; shift ;;
|
|
109
131
|
--to) [[ $# -lt 2 ]] && { clast_porcelain_log_error "retro: --to requires a value"; return 2; }; to="$2"; shift 2 ;;
|
|
110
132
|
--to=*) to="${1#*=}"; shift ;;
|
|
133
|
+
--all) all=1; shift ;;
|
|
111
134
|
--window) [[ $# -lt 2 ]] && { clast_porcelain_log_error "retro: --window requires a value"; return 2; }; window="$2"; shift 2 ;;
|
|
112
135
|
--window=*) window="${1#*=}"; shift ;;
|
|
113
136
|
--refresh) refresh=1; shift ;;
|
|
@@ -118,9 +141,17 @@ clast_cmd_retro() {
|
|
|
118
141
|
esac
|
|
119
142
|
done
|
|
120
143
|
|
|
144
|
+
# An unbounded retro means one model call per session across the whole corpus
|
|
145
|
+
# (slow and costly). Default to the last 7 days unless the caller bounded the
|
|
146
|
+
# window themselves (--from/--to) or opted into everything with --all.
|
|
147
|
+
if (( ! all )) && [[ -z "$from" && -z "$to" ]]; then
|
|
148
|
+
from="-7d"
|
|
149
|
+
fi
|
|
150
|
+
|
|
121
151
|
clast_porcelain_preflight_llm
|
|
122
152
|
|
|
123
153
|
# Structure + per-session bodies from the deterministic core.
|
|
154
|
+
_clast_retro_progress "building work-day manifest..."
|
|
124
155
|
local -a pl=(--json retro --bodies --window "$window")
|
|
125
156
|
[[ -n "$from" ]] && pl+=(--from "$from")
|
|
126
157
|
[[ -n "$to" ]] && pl+=(--to "$to")
|
|
@@ -131,12 +162,26 @@ clast_cmd_retro() {
|
|
|
131
162
|
clast_porcelain_die "retro: ${msg:-failed to build manifest}" 2
|
|
132
163
|
fi
|
|
133
164
|
|
|
165
|
+
# Report the resolved window + discovered work days before the slow summarize
|
|
166
|
+
# loop — the "dates it resolved to" the user wants to see up front.
|
|
167
|
+
local pf pt pw ndays nsess daylist
|
|
168
|
+
pf="$(jq -r '.from // "(start)"' <<<"$manifest")"
|
|
169
|
+
pt="$(jq -r '.to // "(end)"' <<<"$manifest")"
|
|
170
|
+
pw="$(jq -r '.window' <<<"$manifest")"
|
|
171
|
+
ndays="$(jq '.days | length' <<<"$manifest")"
|
|
172
|
+
nsess="$(jq '[.days[].projects[].sessions[]] | length' <<<"$manifest")"
|
|
173
|
+
daylist="$(jq -r '[.days[].day] | join(", ")' <<<"$manifest")"
|
|
174
|
+
_clast_retro_progress "window: $pf -> $pt ($pw)"
|
|
175
|
+
_clast_retro_progress "resolved $nsess session(s) across $ndays day(s): $daylist"
|
|
176
|
+
|
|
134
177
|
local cache_dir
|
|
135
178
|
cache_dir="$(_clast_retrosum_journal_dir)/.retro-summaries"
|
|
136
179
|
|
|
137
180
|
# Summarize each session; collect key → summary.
|
|
138
181
|
local -a summary_pairs=()
|
|
139
|
-
local sess sid key cache_id project work_day body title summary
|
|
182
|
+
local sess sid key cache_id project work_day body title summary pname
|
|
183
|
+
local t0 t1 dt rc
|
|
184
|
+
local idx=0 total="$nsess" queried=0 model_total="0.0"
|
|
140
185
|
while IFS= read -r sess; do
|
|
141
186
|
[[ -z "$sess" ]] && continue
|
|
142
187
|
sid="$(jq -r '.session_id // ""' <<<"$sess")"
|
|
@@ -153,14 +198,35 @@ clast_cmd_retro() {
|
|
|
153
198
|
work_day="$(jq -r '.work_day' <<<"$sess")"
|
|
154
199
|
body="$(jq -r '.body // ""' <<<"$sess")"
|
|
155
200
|
project="$(jq -r '.project_path // "(no project)"' <<<"$sess")"
|
|
201
|
+
idx=$(( idx + 1 ))
|
|
202
|
+
pname="$(jq -r '.progress_project // .project_path // "(no project)"' <<<"$sess")"
|
|
203
|
+
title="$(jq -r '.title // ""' <<<"$sess")"
|
|
204
|
+
_clast_retro_progress "[$idx/$total] $work_day $pname ${title:-${sid:0:8}}"
|
|
156
205
|
if [[ -z "$body" ]]; then
|
|
157
206
|
summary="(no body to summarize)"
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
207
|
+
else
|
|
208
|
+
# Time the call so we can report how long the model took. rc distinguishes
|
|
209
|
+
# a cache hit (0, instant) from a fresh model call (3) from a failure.
|
|
210
|
+
rc=0
|
|
211
|
+
t0="$(clast_porcelain_now)"
|
|
212
|
+
summary="$(_clast_retrosum_summary "$project" "$work_day" "$cache_id" "$body" "$cache_dir" "$refresh")" || rc=$?
|
|
213
|
+
t1="$(clast_porcelain_now)"
|
|
214
|
+
case "$rc" in
|
|
215
|
+
0) : ;; # served from cache — no model call, nothing to time
|
|
216
|
+
3) dt="$(clast_porcelain_elapsed "$t0" "$t1")"
|
|
217
|
+
queried=$(( queried + 1 ))
|
|
218
|
+
model_total="$(awk -v a="$model_total" -v d="$dt" 'BEGIN { printf "%.1f", a + d }')"
|
|
219
|
+
_clast_retro_progress " done in ${dt}s (model total ${model_total}s)" ;;
|
|
220
|
+
*) clast_porcelain_warn "summary failed for session ${sid:-<no id>} — leaving it unsummarized"
|
|
221
|
+
summary="(summary unavailable)" ;;
|
|
222
|
+
esac
|
|
161
223
|
fi
|
|
162
224
|
summary_pairs+=("$(jq -cn --arg k "$key" --arg v "$summary" '{key:$k, summary:$v}')")
|
|
163
|
-
done < <(jq -c '.days[].projects[].sessions[]' <<<"$manifest")
|
|
225
|
+
done < <(jq -c '.days[].projects[] | .project_name as $pn | .sessions[] | . + {progress_project: $pn}' <<<"$manifest")
|
|
226
|
+
|
|
227
|
+
if (( total > 0 )); then
|
|
228
|
+
_clast_retro_progress "summarized $total session(s); $queried queried the model (${model_total}s)"
|
|
229
|
+
fi
|
|
164
230
|
|
|
165
231
|
# Fold summaries back into the manifest and drop the raw bodies. The manifest
|
|
166
232
|
# carries --bodies only as summarizer input; neither the JSON nor the render
|
|
@@ -27,11 +27,41 @@ _clast_wake_separator() {
|
|
|
27
27
|
printf '── %s %s\n' "$label" "$dashes"
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
# ---
|
|
30
|
+
# --- Usage / preflight -------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
_clast_wake_usage() {
|
|
33
|
+
cat <<'EOF'
|
|
34
|
+
Usage: clast wake [--auto]
|
|
35
|
+
|
|
36
|
+
Interactive day curation: for each uncurated session, generate a draft journal
|
|
37
|
+
entry with the LLM and accept/edit/dismiss/skip it.
|
|
38
|
+
|
|
39
|
+
Flags:
|
|
40
|
+
--auto Non-interactive: auto-accept every generated draft and write it.
|
|
41
|
+
Skips the triage menu and the per-session prompt, and does not
|
|
42
|
+
require a tty — suitable for cron/scripts. Sessions whose draft
|
|
43
|
+
fails to generate are skipped. The scan window still honors
|
|
44
|
+
CLAST_WAKE_SINCE (default -14d).
|
|
45
|
+
-h, --help Print this usage and exit.
|
|
46
|
+
|
|
47
|
+
Env:
|
|
48
|
+
CLAST_WAKE_SINCE Scan window (default -14d).
|
|
49
|
+
CLAST_WAKE_AUTO_MIN_CHARS In --auto, skip drafts whose body is shorter than
|
|
50
|
+
this many characters (likely trivial sessions);
|
|
51
|
+
they stay uncurated for a later interactive pass.
|
|
52
|
+
Default 60. Set 0 to write every draft.
|
|
53
|
+
|
|
54
|
+
Requires the CLAST_LLM_* env vars (see `clast --help`).
|
|
55
|
+
EOF
|
|
56
|
+
}
|
|
31
57
|
|
|
58
|
+
# _clast_wake_preflight <auto>
|
|
59
|
+
# In interactive mode (auto=0) a tty is required so we can read choices; --auto
|
|
60
|
+
# reads nothing from the terminal, so that check is skipped.
|
|
32
61
|
_clast_wake_preflight() {
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
local auto="${1:-0}"
|
|
63
|
+
if (( ! auto )) && [[ ! -t 0 ]]; then
|
|
64
|
+
clast_porcelain_die "clast wake requires an interactive terminal (stdin is not a tty). Use --auto for non-interactive curation."
|
|
35
65
|
fi
|
|
36
66
|
clast_porcelain_preflight_llm
|
|
37
67
|
}
|
|
@@ -239,7 +269,19 @@ _clast_wake_triage() {
|
|
|
239
269
|
# --- Main --------------------------------------------------------------------
|
|
240
270
|
|
|
241
271
|
clast_cmd_wake() {
|
|
242
|
-
|
|
272
|
+
local auto=0
|
|
273
|
+
while [[ $# -gt 0 ]]; do
|
|
274
|
+
case "$1" in
|
|
275
|
+
--auto) auto=1; shift ;;
|
|
276
|
+
-h|--help) _clast_wake_usage; return 0 ;;
|
|
277
|
+
--) shift; break ;;
|
|
278
|
+
*) clast_porcelain_log_error "wake: unknown argument '$1'"; return 2 ;;
|
|
279
|
+
esac
|
|
280
|
+
done
|
|
281
|
+
|
|
282
|
+
_clast_wake_preflight "$auto"
|
|
283
|
+
|
|
284
|
+
(( auto )) && clast_porcelain_info "Auto mode: drafts will be accepted without review."
|
|
243
285
|
|
|
244
286
|
clast_porcelain_info "Snapshotting fresh transcripts..."
|
|
245
287
|
if ! clast-plumbing snapshot 2>/dev/null; then
|
|
@@ -299,7 +341,8 @@ clast_cmd_wake() {
|
|
|
299
341
|
last_day="$(jq -r '[.[].day_bucket] | sort | last' <<<"$uncurated")"
|
|
300
342
|
project_count="$(jq '[.[].project] | unique | length' <<<"$uncurated")"
|
|
301
343
|
|
|
302
|
-
|
|
344
|
+
# Triage is an interactive scope picker; --auto processes the whole window.
|
|
345
|
+
if (( ! auto )) && (( day_count > 1 )); then
|
|
303
346
|
uncurated="$(_clast_wake_triage "$uncurated" "$total" "$day_count" "$first_day" "$last_day" "$project_count")"
|
|
304
347
|
total="$(jq 'length' <<<"$uncurated")"
|
|
305
348
|
if (( total == 0 )); then
|
|
@@ -315,6 +358,15 @@ clast_cmd_wake() {
|
|
|
315
358
|
local curated_count=0 skipped_count=0 dismissed_count=0
|
|
316
359
|
local -a curated_projects=()
|
|
317
360
|
local i=0 stop=0
|
|
361
|
+
# Cumulative model time across every draft generation (excludes the time the
|
|
362
|
+
# reviewer spends at the menu), so the run's LLM cost is legible — mirrors the
|
|
363
|
+
# per-call + total timing `clast retro` reports.
|
|
364
|
+
local wake_model_total="0.0"
|
|
365
|
+
# --auto guard: skip (do NOT write) a draft whose body is shorter than this
|
|
366
|
+
# many characters — likely a trivial/low-value session. Skipped sessions stay
|
|
367
|
+
# uncurated for a later interactive pass, so this is safe to tune aggressively.
|
|
368
|
+
# 0 disables the guard. Ignored in interactive mode (the reviewer decides).
|
|
369
|
+
local auto_min_chars="${CLAST_WAKE_AUTO_MIN_CHARS:-60}"
|
|
318
370
|
|
|
319
371
|
while (( i < total && stop == 0 )); do
|
|
320
372
|
local session
|
|
@@ -349,7 +401,10 @@ clast_cmd_wake() {
|
|
|
349
401
|
local recorded="$rec_date"
|
|
350
402
|
if [[ -n "$start_short" ]]; then
|
|
351
403
|
recorded="$recorded $start_short"
|
|
352
|
-
|
|
404
|
+
# Brace the expansion: the separator is an en dash (U+2013), and bash in a
|
|
405
|
+
# UTF-8 locale reads its bytes as part of the identifier — `$recorded–` then
|
|
406
|
+
# expands as the unset name `recorded–` and `set -u` kills the run.
|
|
407
|
+
[[ -n "$end_short" && "$end_short" != "$start_short" ]] && recorded="${recorded}–${end_short}"
|
|
353
408
|
recorded="$recorded $tz"
|
|
354
409
|
fi
|
|
355
410
|
|
|
@@ -419,9 +474,15 @@ Revisions requested by user: ${edit_extra}"
|
|
|
419
474
|
fi
|
|
420
475
|
|
|
421
476
|
clast_porcelain_info "Generating draft..."
|
|
477
|
+
local gen_t0 gen_t1 gen_dt
|
|
478
|
+
gen_t0="$(clast_porcelain_now)"
|
|
422
479
|
if ! draft="$(clast_porcelain_llm_chat "$full_system" "$full_user")"; then
|
|
423
480
|
printf '\n'
|
|
424
481
|
clast_porcelain_warn "LLM call failed for session $sid"
|
|
482
|
+
# No tty to prompt in auto mode — skip this session and move on.
|
|
483
|
+
if (( auto )); then
|
|
484
|
+
skipped_count=$(( skipped_count + 1 )); break
|
|
485
|
+
fi
|
|
425
486
|
local retry
|
|
426
487
|
printf ' [r] Retry [s] Skip [q] Stop\n Choice: '
|
|
427
488
|
read -r -n1 retry </dev/tty
|
|
@@ -432,12 +493,37 @@ Revisions requested by user: ${edit_extra}"
|
|
|
432
493
|
*) skipped_count=$(( skipped_count + 1 )); break ;;
|
|
433
494
|
esac
|
|
434
495
|
fi
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
496
|
+
# Only reached when the draft succeeded (every failure branch above
|
|
497
|
+
# continues or breaks), so this times just the model call.
|
|
498
|
+
gen_t1="$(clast_porcelain_now)"
|
|
499
|
+
gen_dt="$(clast_porcelain_elapsed "$gen_t0" "$gen_t1")"
|
|
500
|
+
wake_model_total="$(awk -v a="$wake_model_total" -v d="$gen_dt" 'BEGIN { printf "%.1f", a + d }')"
|
|
501
|
+
clast_porcelain_info " done in ${gen_dt}s (model total ${wake_model_total}s)"
|
|
438
502
|
|
|
439
503
|
local choice
|
|
440
|
-
|
|
504
|
+
if (( auto )); then
|
|
505
|
+
# Guard unattended writes: skip a draft whose body is below the minimum
|
|
506
|
+
# length (likely a trivial session). Skipped — not dismissed — so it
|
|
507
|
+
# stays uncurated and can be handled in a later interactive pass.
|
|
508
|
+
local auto_body auto_len
|
|
509
|
+
auto_body="$(_clast_wake_strip_tags_trailer "$draft")"
|
|
510
|
+
auto_body="${auto_body#"${auto_body%%[![:space:]]*}"}" # ltrim
|
|
511
|
+
auto_body="${auto_body%"${auto_body##*[![:space:]]}"}" # rtrim
|
|
512
|
+
auto_len=${#auto_body}
|
|
513
|
+
if (( auto_min_chars > 0 && auto_len < auto_min_chars )); then
|
|
514
|
+
clast_porcelain_info " Draft below threshold (${auto_len} < ${auto_min_chars} chars) — skipping (stays uncurated)."
|
|
515
|
+
skipped_count=$(( skipped_count + 1 ))
|
|
516
|
+
drafting=0
|
|
517
|
+
continue
|
|
518
|
+
fi
|
|
519
|
+
# Auto-accept without printing the full draft or prompting — the write
|
|
520
|
+
# result below records what landed. Reuses the `a` case verbatim.
|
|
521
|
+
choice="a"
|
|
522
|
+
else
|
|
523
|
+
printf '\n%s\n' "$draft"
|
|
524
|
+
printf '\n'
|
|
525
|
+
choice="$(_clast_wake_prompt_choice)"
|
|
526
|
+
fi
|
|
441
527
|
|
|
442
528
|
case "$choice" in
|
|
443
529
|
a|A)
|
|
@@ -511,4 +597,5 @@ Revisions requested by user: ${edit_extra}"
|
|
|
511
597
|
if (( remaining > 0 )); then
|
|
512
598
|
clast_porcelain_info " Remaining: $remaining session(s) (stopped early)"
|
|
513
599
|
fi
|
|
600
|
+
clast_porcelain_info " Model time: ${wake_model_total}s"
|
|
514
601
|
}
|
package/package.json
CHANGED
package/skills/wake/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wake
|
|
3
|
-
description: 'Generate curated journal entries from yesterday''s Claude Code sessions across all projects. Use when the user says "/wake", "wake", "morning briefing", "catch me up on yesterday", "what did I work on yesterday", "review my day", "process yesterday''s sessions", or otherwise signals they want to curate prior work across projects at the start of a new day. Runs `clast-plumbing snapshot` to ensure fresh data, then walks through each uncurated session from yesterday and proposes a draft entry the user can accept, edit, or skip. Prompts for promotion of decisions, common-issues, and workflows per accepted session. This is the once-per-day curation flow; for per-project briefings use /brief; for mid-session pivots use session-brief.'
|
|
3
|
+
description: 'Generate curated journal entries from yesterday''s Claude Code sessions across all projects. Use when the user says "/wake", "wake", "morning briefing", "catch me up on yesterday", "what did I work on yesterday", "review my day", "process yesterday''s sessions", or otherwise signals they want to curate prior work across projects at the start of a new day. Runs `clast-plumbing snapshot` to ensure fresh data, then walks through each uncurated session from yesterday and proposes a draft entry the user can accept, edit, or skip. Prompts for promotion of decisions, common-issues, and workflows per accepted session. Supports an opt-in auto mode when the user explicitly asks to curate everything without review ("do them all", "don''t ask me") — the equivalent of `clast wake --auto`. This is the once-per-day curation flow; for per-project briefings use /brief; for mid-session pivots use session-brief.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Wake
|
|
@@ -81,7 +81,7 @@ print "Nothing to curate — all remaining sessions were empty or slash-command-
|
|
|
81
81
|
|
|
82
82
|
### Triage when multiple days have uncurated sessions
|
|
83
83
|
|
|
84
|
-
If uncurated sessions span more than one day (e.g., after a weekend or break), present a triage step before processing. Show a per-day breakdown:
|
|
84
|
+
If uncurated sessions span more than one day (e.g., after a weekend or break), present a triage step before processing. (In [Auto mode](#auto-mode) skip triage entirely and process the whole window.) Show a per-day breakdown:
|
|
85
85
|
|
|
86
86
|
```
|
|
87
87
|
Found 45 uncurated sessions across 5 days (2026-05-26 to 2026-05-30).
|
|
@@ -128,7 +128,7 @@ For each session in the list:
|
|
|
128
128
|
|
|
129
129
|
4. Display the draft to the user inside a fenced markdown code block, with a brief preamble: "Here's a draft for the X session in <project> at HH:MM:".
|
|
130
130
|
|
|
131
|
-
5. Present the **promotion question** (see below) via AskUserQuestion.
|
|
131
|
+
5. Present the **promotion question** (see below) via AskUserQuestion. (In [Auto mode](#auto-mode) skip this and accept the draft, subject to the length guard.)
|
|
132
132
|
|
|
133
133
|
6. Handle the response:
|
|
134
134
|
- **Accept** (any combination of accept-flavored options): pipe the draft to `clast-plumbing entries write` via stdin.
|
|
@@ -147,6 +147,8 @@ Auto-dismissed (no-op): 5 sessions.
|
|
|
147
147
|
Skipped: 1 session.
|
|
148
148
|
Remaining uncurated: 0.
|
|
149
149
|
|
|
150
|
+
(In Auto mode, also report: "Skipped (below length threshold): N sessions.")
|
|
151
|
+
|
|
150
152
|
Promoted:
|
|
151
153
|
Decisions: 1
|
|
152
154
|
Common-issues: 0
|
|
@@ -155,6 +157,35 @@ Promoted:
|
|
|
155
157
|
Run `/brief <project>` to start working on a specific project today.
|
|
156
158
|
```
|
|
157
159
|
|
|
160
|
+
## Auto mode
|
|
161
|
+
|
|
162
|
+
By default every session gets its own AskUserQuestion — the friction is the point, it's where
|
|
163
|
+
curation happens. But if the user **explicitly** asks to curate everything without reviewing it
|
|
164
|
+
("do them all", "don't ask me", "no prompts", "just write them all"), switch to auto mode.
|
|
165
|
+
|
|
166
|
+
This mirrors `clast wake --auto` in the CLI porcelain, and behaves the same way:
|
|
167
|
+
|
|
168
|
+
- **Skip the triage step** (Step 2) even when the backlog spans multiple days. The whole scan
|
|
169
|
+
window is processed.
|
|
170
|
+
- **Skip the per-session promotion question** (Step 3, item 5). Accept and write every draft via
|
|
171
|
+
the normal `entries write` path in "Writing the entry" below. Nothing is promoted — promotion
|
|
172
|
+
requires a human choice.
|
|
173
|
+
- **Length guard.** Before writing, take the draft body *without* the suggested-tags trailer and
|
|
174
|
+
trim surrounding whitespace. If it is shorter than `CLAST_WAKE_AUTO_MIN_CHARS` characters
|
|
175
|
+
(default `60`; `0` disables the guard), **skip** it — do not write it, and do **not** dismiss
|
|
176
|
+
it. It stays uncurated so it can be handled in a later interactive pass. Report it as
|
|
177
|
+
`Draft below threshold (N < M chars) — skipping (stays uncurated).`
|
|
178
|
+
- **A draft that fails to generate is skipped**, not retried — there's no reviewer to ask.
|
|
179
|
+
- Step 2a no-op auto-dismissal still runs first, unchanged.
|
|
180
|
+
- Don't print each draft in full; the write confirmation is the record of what landed.
|
|
181
|
+
|
|
182
|
+
Everything else is unchanged. Announce the mode up front ("Auto mode: drafts will be accepted
|
|
183
|
+
without review.") and report the counts in the Step 4 summary, including how many were skipped
|
|
184
|
+
for being under the length threshold.
|
|
185
|
+
|
|
186
|
+
For unattended/cron curation outside a Claude Code session, the CLI equivalent is
|
|
187
|
+
`clast wake --auto`.
|
|
188
|
+
|
|
158
189
|
## Draft generation prompt
|
|
159
190
|
|
|
160
191
|
The prompt templates are installed alongside the plugin under `$CLAUDE_PLUGIN_ROOT/lib/clast/prompts/`:
|
|
@@ -225,4 +256,4 @@ For promoted items (decisions, common-issues, workflows): currently these are tr
|
|
|
225
256
|
- **Multi-day backlog**: present the triage step (Step 2) so the user can choose scope before processing.
|
|
226
257
|
- **`clast-plumbing snapshot` fails**: warn the user, then attempt to proceed with whatever's already in the manifest.
|
|
227
258
|
- **`clast-plumbing show` fails for a specific session**: skip that session, note it in the final summary, continue with the rest.
|
|
228
|
-
- **User says "do them all without prompting"**:
|
|
259
|
+
- **User says "do them all without prompting"**: switch to [Auto mode](#auto-mode) — accept and write every draft, skipping triage and the per-session question, with the `CLAST_WAKE_AUTO_MIN_CHARS` length guard applied. This is opt-in only: the interactive per-session AskUserQuestion stays the default, because the friction is where curation happens.
|