tickmarkr 2.4.0 → 2.4.1

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 (45) hide show
  1. package/dist/adapters/catalog-remote.d.ts +11 -0
  2. package/dist/adapters/catalog-remote.js +55 -13
  3. package/dist/adapters/codex.js +6 -7
  4. package/dist/adapters/qwen.js +3 -3
  5. package/dist/adapters/registry.js +13 -1
  6. package/dist/cli/commands/compile.d.ts +3 -0
  7. package/dist/cli/commands/compile.js +77 -46
  8. package/dist/cli/commands/doctor.js +14 -9
  9. package/dist/cli/commands/fleet.js +11 -5
  10. package/dist/cli/commands/init.js +12 -13
  11. package/dist/cli/commands/plan.js +47 -7
  12. package/dist/cli/commands/run.js +20 -1
  13. package/dist/cli/commands/status.js +20 -21
  14. package/dist/cli/commands/version.js +2 -2
  15. package/dist/compile/collateral.d.ts +14 -5
  16. package/dist/compile/collateral.js +32 -26
  17. package/dist/compile/index.js +17 -6
  18. package/dist/compile/native.js +10 -2
  19. package/dist/compile/ownership.js +15 -9
  20. package/dist/drivers/herdr.d.ts +1 -0
  21. package/dist/drivers/herdr.js +32 -3
  22. package/dist/drivers/orca.d.ts +5 -1
  23. package/dist/drivers/orca.js +51 -2
  24. package/dist/drivers/types.d.ts +1 -1
  25. package/dist/gates/baseline.d.ts +26 -2
  26. package/dist/gates/baseline.js +90 -7
  27. package/dist/gates/review.d.ts +2 -2
  28. package/dist/gates/review.js +2 -18
  29. package/dist/graph/graph.d.ts +20 -0
  30. package/dist/graph/graph.js +66 -1
  31. package/dist/route/preference.d.ts +3 -1
  32. package/dist/route/preference.js +4 -4
  33. package/dist/run/consult.js +1 -0
  34. package/dist/run/daemon.d.ts +5 -0
  35. package/dist/run/daemon.js +151 -22
  36. package/dist/run/git.d.ts +2 -0
  37. package/dist/run/git.js +18 -4
  38. package/dist/run/journal.d.ts +1 -1
  39. package/dist/run/journal.js +1 -1
  40. package/dist/run/lock.d.ts +6 -0
  41. package/dist/run/lock.js +41 -1
  42. package/package.json +1 -1
  43. package/skills/tickmarkr-overseer/SKILL.md +39 -4
  44. package/skills/tickmarkr-overseer/scripts/grade-ci.sh +79 -0
  45. package/skills/tickmarkr-overseer/scripts/watch-context.sh +90 -4
@@ -0,0 +1,79 @@
1
+ #!/bin/bash
2
+ # grade-ci.sh <run-id> <expected-count> [tag] — grade both public CI jobs from their JOB LOGS.
3
+ # Grade only at run-end. A missing/in-progress/empty log is UNREADABLE, never evidence of green.
4
+ # Tri-state: exit 0 GREEN, 1 RED, 2 UNREADABLE. UNREADABLE dominates a mixed result.
5
+ set -u
6
+
7
+ run=${1:?run id required}
8
+ expected=${2:?expected tracked test-file count required}
9
+ tag=${3:-$run}
10
+ repo=${TKR_GRADE_CI_REPO:-alzahrani-khalid/tickmarkr}
11
+ out_dir=${TKR_GRADE_CI_DIR:-${TKR_STATE_DIR:-.tickmarkr}/overseer/diag}
12
+ mkdir -p "$out_dir" || { echo "UNREADABLE: cannot create log directory $out_dir"; exit 2; }
13
+
14
+ verdict=0
15
+ mark_unreadable() { verdict=2; }
16
+ mark_red() { [ "$verdict" -eq 0 ] && verdict=1; }
17
+
18
+ jobs=$(gh run view "$run" --repo "$repo" --json jobs \
19
+ --jq '.jobs[] | [.databaseId, .name, .status, (.conclusion // "")] | @tsv') \
20
+ || { echo "UNREADABLE: job list"; exit 2; }
21
+ [ -n "$jobs" ] || { echo "UNREADABLE: empty job list"; exit 2; }
22
+
23
+ seen_test=0
24
+ seen_macos=0
25
+ while IFS=$'\t' read -r id name status conclusion; do
26
+ case "$name" in
27
+ test) seen_test=1 ;;
28
+ test-macos) seen_macos=1 ;;
29
+ *) continue ;;
30
+ esac
31
+
32
+ echo "$name: status=$status conclusion=${conclusion:-none} job=$id"
33
+ if [ "$status" != "completed" ]; then
34
+ echo "$name: UNREADABLE (job has not reached run-end)"
35
+ mark_unreadable
36
+ continue
37
+ fi
38
+
39
+ log="$out_dir/CI-$tag-$name.log"
40
+ gh run view --repo "$repo" --job "$id" --log > "$log" 2>/dev/null
41
+ if [ ! -s "$log" ]; then
42
+ echo "$name: UNREADABLE (empty log)"
43
+ mark_unreadable
44
+ continue
45
+ fi
46
+
47
+ oracle=$(grep -oE 'COUNT_ORACLE [A-Z]+ expected=[0-9A-Z]+ actual=[0-9A-Z]+' "$log" | tail -1)
48
+ files=$(grep -oE 'Test Files .*' "$log" | sed 's/[[:space:]]*$//')
49
+ passed=$(printf '%s\n' "$files" | grep -oE '[0-9]+ passed' | awk '{s+=$1} END{print s+0}')
50
+ skipped=$(printf '%s\n' "$files" | grep -oE '[0-9]+ skipped' | awk '{s+=$1} END{print s+0}')
51
+ failed=$(printf '%s\n' "$files" | grep -oE '[0-9]+ failed' | awk '{s+=$1} END{print s+0}')
52
+ timedout=$(grep -cE 'Test timed out|Error: Hook timed out' "$log" || true)
53
+ errors=$(grep -oE '##\[error\].*' "$log" | sort | uniq -c | sed 's/^ *//' | tr '\n' ';')
54
+
55
+ echo "$name: oracle=[${oracle:-MISSING}] files=[$(printf '%s' "$files" | tr '\n' '|')] passed=$passed skipped=$skipped failed=$failed timedout=$timedout errors=[$errors]"
56
+ if [ -z "$oracle" ] || [ -z "$files" ]; then
57
+ echo "$name: UNREADABLE"
58
+ mark_unreadable
59
+ elif [ "$oracle" = "COUNT_ORACLE GREEN expected=$expected actual=$expected" ] \
60
+ && [ "$failed" -eq 0 ] && [ "$timedout" -eq 0 ] \
61
+ && [ $((passed + skipped)) -eq "$expected" ]; then
62
+ echo "$name: GREEN"
63
+ else
64
+ echo "$name: RED"
65
+ mark_red
66
+ fi
67
+ done <<< "$jobs"
68
+
69
+ if [ "$seen_test" -ne 1 ]; then
70
+ echo "test: UNREADABLE (job missing from run)"
71
+ mark_unreadable
72
+ fi
73
+ if [ "$seen_macos" -ne 1 ]; then
74
+ echo "test-macos: UNREADABLE (job missing from run)"
75
+ mark_unreadable
76
+ fi
77
+
78
+ echo "VERDICT rc=$verdict (0=all GREEN 1=RED 2=UNREADABLE)"
79
+ exit "$verdict"
@@ -36,7 +36,7 @@
36
36
  # TKR_AUTO_CLEAR=1 at act-pct WITH a fresh handoff, auto-clear orchestrator/overseer; other roles wake only.
37
37
  # TKR_REBRIEF=<path> the file the re-briefed seat is told to read (defaults to the handoff).
38
38
  # TKR_HANDOFF_MAX_AGE_S how fresh "fresh" is (default 900).
39
- # TKR_CLEAR_SETTLE_S seconds to let a cleared seat settle before the re-brief (default 6).
39
+ # TKR_CLEAR_SETTLE_S seconds allowed for the cleared banner percentage to drop (default 6).
40
40
  # TKR_BLIND_ALARM_S seconds unreadable before CONTEXT_BLIND alarms (default 120).
41
41
  # TKR_CONTEXT_WINDOW context window in tokens. Set it when the banner does not name one;
42
42
  # NEVER guessed — a wrong denominator makes every warn and act line wrong.
@@ -234,6 +234,82 @@ context_pct() {
234
234
  banner_pct
235
235
  }
236
236
 
237
+ status_of() {
238
+ herdr agent get "$TARGET" 2>/dev/null \
239
+ | sed -n 's/.*"agent_status":"\([a-z_]*\)".*/\1/p' | head -1
240
+ }
241
+
242
+ # A clear is a SEND, so it inherits the verified-send precondition: the target must be between turns
243
+ # and its live prompt line must contain no typed draft. Claude Code's autosuggest is text in the plain
244
+ # rendering but is wrapped in SGR dim in the ANSI rendering; it is decoration, not a draft. Parse the
245
+ # Esc-prefixed SGR bytes instead of treating every visible character after the prompt glyph as state.
246
+ prompt_is_empty_or_dim() {
247
+ herdr agent read "$TARGET" --source visible --lines 14 --format ansi 2>/dev/null | python3 -c '
248
+ import re, sys
249
+ screen = sys.stdin.read().splitlines()
250
+ ansi = re.compile(r"\x1b\[([0-9;]*)m")
251
+ prompt = None
252
+ for line in screen:
253
+ plain = ansi.sub("", line).lstrip()
254
+ if plain.startswith(("❯", "›")):
255
+ prompt = line
256
+ if prompt is None:
257
+ sys.exit(1)
258
+ glyph = min((i for i in (prompt.find("❯"), prompt.find("›")) if i >= 0), default=-1)
259
+ if glyph < 0:
260
+ sys.exit(1)
261
+ payload = prompt[glyph + 1:]
262
+ dim = False
263
+ pos = 0
264
+ for match in ansi.finditer(payload):
265
+ if payload[pos:match.start()].strip() and not dim:
266
+ sys.exit(1)
267
+ codes = [int(c) if c else 0 for c in match.group(1).split(";")]
268
+ if 0 in codes:
269
+ dim = False
270
+ if 2 in codes:
271
+ dim = True
272
+ if 22 in codes:
273
+ dim = False
274
+ pos = match.end()
275
+ if payload[pos:].strip() and not dim:
276
+ sys.exit(1)
277
+ ' 2>/dev/null
278
+ }
279
+
280
+ clear_target_ready() {
281
+ CLEAR_STATUS=$(status_of)
282
+ case "$CLEAR_STATUS" in
283
+ idle|done) ;;
284
+ *) CLEAR_REASON="status=${CLEAR_STATUS:-unreadable}"; return 1 ;;
285
+ esac
286
+ if ! prompt_is_empty_or_dim; then
287
+ CLEAR_REASON="status=$CLEAR_STATUS prompt=typed-or-unreadable"
288
+ return 1
289
+ fi
290
+ return 0
291
+ }
292
+
293
+ # `/clear` has landed only when a fresh banner read says its context percentage is lower. The JSONL
294
+ # source deliberately does not participate in this receipt: it names the pre-clear session and can
295
+ # remain at the old fill while the new prompt is already visible. No drop means no re-brief and no
296
+ # CONTEXT_CLEARED claim.
297
+ wait_for_clear_receipt() {
298
+ local before="$1" waited=0 after
299
+ case "$SETTLE" in *[!0-9]*|'') return 1 ;; esac
300
+ while :; do
301
+ after=$(banner_pct 2>/dev/null) || after=""
302
+ if printf '%s\n' "$after" | grep -qE '^[0-9]+$' \
303
+ && [ "$after" -lt "$before" ] 2>/dev/null; then
304
+ printf '%s\n' "$after"
305
+ return 0
306
+ fi
307
+ [ "$waited" -lt "$SETTLE" ] || return 1
308
+ sleep 1
309
+ waited=$((waited + 1))
310
+ done
311
+ }
312
+
237
313
  handoff_fresh() {
238
314
  [ -n "$HANDOFF" ] || return 1
239
315
  [ -f "$HANDOFF" ] || return 1
@@ -245,13 +321,23 @@ handoff_fresh() {
245
321
  }
246
322
 
247
323
  act_on() {
248
- local P="$1"
324
+ local P="$1" BEFORE AFTER
249
325
  if handoff_fresh; then
250
326
  if [ "${TKR_AUTO_CLEAR:-0}" = "1" ] && { [ "$ROLE" = "orchestrator" ] || [ "$ROLE" = "overseer" ]; }; then
327
+ if ! clear_target_ready; then
328
+ echo "CONTEXT_ACT_DEFERRED $TARGET at ${P}% — ${CLEAR_REASON}; waiting for idle and an empty/dim-only prompt line"
329
+ return 0
330
+ fi
331
+ BEFORE=$(banner_pct 2>/dev/null) || BEFORE=""
332
+ case "$BEFORE" in ''|*[!0-9]*) echo "CONTEXT_CLEAR_UNVERIFIED $TARGET — no numeric banner baseline; not clearing"; return 0 ;; esac
251
333
  herdr agent prompt "$TARGET" "/clear" >/dev/null 2>&1
252
- sleep "$SETTLE"
334
+ if ! AFTER=$(wait_for_clear_receipt "$BEFORE"); then
335
+ echo "CONTEXT_CLEAR_UNVERIFIED $TARGET at ${P}% — banner did not re-read below ${BEFORE}% within ${SETTLE}s"
336
+ echo " no re-brief sent and CONTEXT_CLEARED withheld; inspect the seat before acting again"
337
+ exit 0
338
+ fi
253
339
  herdr agent prompt "$TARGET" "Read ${REBRIEF} and continue exactly where it says. Your context was cleared at ${P}% against that handoff; it is current as of $(date '+%H:%M'). The same-process clear kept every background task alive: kill only this seat's recorded watcher pids first, verify the process table twice, then re-arm. Do not reconstruct from memory — everything you need is on disk." >/dev/null 2>&1
254
- echo "CONTEXT_CLEARED $TARGET at ${P}% — handoff fresh, re-briefed from ${REBRIEF}"
340
+ echo "CONTEXT_CLEARED $TARGET ${P}% -> ${AFTER}% — banner re-read lower, handoff fresh, re-briefed from ${REBRIEF}"
255
341
  exit 0
256
342
  fi
257
343
  echo "CONTEXT_ACT $TARGET ${P}% (>= ${ACT}) — handoff is FRESH, a clear is SAFE now"